@decohere/forge 0.0.1-dev.111 → 0.0.1-dev.112

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.
@@ -135,11 +135,11 @@ class SwcTranspiler extends (0, _extensibility.mixwith)(_cachesoutputsmixin.Cach
135
135
  * Async variant of {@link transformFileSync}, with the same result for the same file. SWC
136
136
  * runs it on libuv's thread pool, so many calls in flight compile in parallel (#661).
137
137
  */ async transformFileAsync(file, buildTarget, options = {}, baseConfig = this.createBaseConfig(buildTarget, options)) {
138
- const swcOptions = this.#swcConfigFactory.createFileConfig(file, baseConfig);
139
138
  let content;
140
139
  try {
141
140
  content = await _nodefs.default.promises.readFile(file.fullPath, "utf8");
142
141
  } catch (error) {
142
+ const swcOptions = this.#swcConfigFactory.createFileConfig(file, baseConfig);
143
143
  return _transformresponsemodel.TransformResponse.Error({
144
144
  file,
145
145
  messages: [
@@ -148,6 +148,14 @@ class SwcTranspiler extends (0, _extensibility.mixwith)(_cachesoutputsmixin.Cach
148
148
  config: swcOptions
149
149
  });
150
150
  }
151
+ return this.transformCode(content, file, buildTarget, baseConfig);
152
+ }
153
+ /**
154
+ * Compiles `content` as the source of `file`, without reading the file: for callers that
155
+ * already hold the code, such as the Vitest plugin, which must compile what Vite passes it
156
+ * rather than an earlier read of the disk (#517). Async SWC, on the thread pool.
157
+ */ async transformCode(content, file, buildTarget, baseConfig) {
158
+ const swcOptions = this.#swcConfigFactory.createFileConfig(file, baseConfig);
151
159
  if (content === "") return SwcTranspiler.skippedEmpty(file, swcOptions);
152
160
  try {
153
161
  const transformed = await (0, _core.transform)(content, swcOptions);
@@ -43,9 +43,11 @@ function forgeBuild(options) {
43
43
  }
44
44
  });
45
45
  const logger = loggerFactory(PLUGIN_NAME);
46
- const skipBuild = options?.skipBuild ?? false;
47
46
  const transpiler = new _swctranspiler.SwcTranspiler();
48
- const warmedProjects = new Set();
47
+ /**
48
+ * SWC options per project (tsconfig, `.swcrc`, path aliases), built once: rebuilding them for
49
+ * every file was most of the plugin's per-file cost, as it was in flux (#513, #517).
50
+ */ const baseConfigs = new Map();
49
51
  const transformCache = new Map();
50
52
  let workspace = null;
51
53
  const initWorkspace = async ()=>{
@@ -56,10 +58,17 @@ function forgeBuild(options) {
56
58
  rootDir: workspaceRoot
57
59
  });
58
60
  };
59
- const preWarmProject = (project)=>{
60
- if (warmedProjects.has(project.projectDirectory)) return;
61
- warmedProjects.add(project.projectDirectory);
62
- void transpiler.transformProject(project).then(()=>logger.debug(`Pre-warm complete for ${project.name}`)).catch((err)=>logger.error(`Pre-warm error for ${project.name}: ${err.message}`));
61
+ const baseConfigFor = (project)=>{
62
+ const cached = baseConfigs.get(project);
63
+ if (cached != null) return cached;
64
+ const config = transpiler.createBaseConfig(_buildtargettype.BuildTargets.es6, {
65
+ aliasPaths: true,
66
+ tsConfig: project?.getTsConfigSync(),
67
+ userConfig: project?.getSwcConfigSync(),
68
+ workspace: workspace ?? undefined
69
+ });
70
+ baseConfigs.set(project, config);
71
+ return config;
63
72
  };
64
73
  return {
65
74
  name: PLUGIN_NAME,
@@ -77,42 +86,24 @@ function forgeBuild(options) {
77
86
  return null;
78
87
  },
79
88
  watchChange (id) {
80
- const file = new _io.FileEx({
81
- fullPath: id
82
- });
83
89
  transformCache.delete(id);
84
- transpiler.invalidateContentCache(file);
85
90
  },
86
91
  async transform (code, id) {
87
92
  if (id.includes("node_modules") || !/[.](ts|tsx|mts|cts)$/.test(id)) {
88
93
  return null;
89
94
  }
90
- await initWorkspace();
91
- const project = await workspace?.findProject(id, buildConfig);
92
- if (project != null && !skipBuild) {
93
- preWarmProject(project);
94
- }
95
95
  const cached = transformCache.get(id);
96
96
  if (cached !== undefined && cached.content === code) {
97
97
  return cached.result;
98
98
  }
99
- const file = new _io.FileEx({
99
+ await initWorkspace();
100
+ const project = await workspace?.findProject(id, buildConfig);
101
+ // The code Vite passes, not a read of the file: no content cache can serve an older
102
+ // version, and there is no pre-warm compiling files ahead from disk (#517).
103
+ const response = await transpiler.transformCode(code, new _io.FileEx({
100
104
  fullPath: id,
101
105
  content: code
102
- });
103
- if (cached !== undefined) {
104
- // Content changed for a previously-seen file — invalidate the transpiler's
105
- // FILE_CONTENT cache so the fresh content from Vite is used.
106
- // The VS Code Vitest extension disables Vite's chokidar watcher
107
- // (server.watch: null) and drives re-runs via vitest.watcher.onFileChange(),
108
- // bypassing watchChange entirely — so we cannot rely on watchChange alone.
109
- transpiler.invalidateContentCache(file);
110
- }
111
- const [response] = await transpiler.transformFile(file, _buildtargettype.BuildTargets.es6, {
112
- aliasPaths: true,
113
- tsConfig: project ? await project.getTsConfig() : undefined,
114
- workspace: workspace ?? undefined
115
- });
106
+ }), _buildtargettype.BuildTargets.es6, baseConfigFor(project));
116
107
  if (!response || response.outcome !== "success") return null;
117
108
  const result = {
118
109
  code: response.code,
@@ -120,11 +120,11 @@ export class SwcTranspiler extends mixwith(CachesOutputs, IncrementalTransform)
120
120
  * Async variant of {@link transformFileSync}, with the same result for the same file. SWC
121
121
  * runs it on libuv's thread pool, so many calls in flight compile in parallel (#661).
122
122
  */ async transformFileAsync(file, buildTarget, options = {}, baseConfig = this.createBaseConfig(buildTarget, options)) {
123
- const swcOptions = this.#swcConfigFactory.createFileConfig(file, baseConfig);
124
123
  let content;
125
124
  try {
126
125
  content = await fs.promises.readFile(file.fullPath, "utf8");
127
126
  } catch (error) {
127
+ const swcOptions = this.#swcConfigFactory.createFileConfig(file, baseConfig);
128
128
  return TransformResponse.Error({
129
129
  file,
130
130
  messages: [
@@ -133,6 +133,14 @@ export class SwcTranspiler extends mixwith(CachesOutputs, IncrementalTransform)
133
133
  config: swcOptions
134
134
  });
135
135
  }
136
+ return this.transformCode(content, file, buildTarget, baseConfig);
137
+ }
138
+ /**
139
+ * Compiles `content` as the source of `file`, without reading the file: for callers that
140
+ * already hold the code, such as the Vitest plugin, which must compile what Vite passes it
141
+ * rather than an earlier read of the disk (#517). Async SWC, on the thread pool.
142
+ */ async transformCode(content, file, buildTarget, baseConfig) {
143
+ const swcOptions = this.#swcConfigFactory.createFileConfig(file, baseConfig);
136
144
  if (content === "") return SwcTranspiler.skippedEmpty(file, swcOptions);
137
145
  try {
138
146
  const transformed = await transform(content, swcOptions);
@@ -28,9 +28,11 @@ export function forgeBuild(options) {
28
28
  }
29
29
  });
30
30
  const logger = loggerFactory(PLUGIN_NAME);
31
- const skipBuild = options?.skipBuild ?? false;
32
31
  const transpiler = new SwcTranspiler();
33
- const warmedProjects = new Set();
32
+ /**
33
+ * SWC options per project (tsconfig, `.swcrc`, path aliases), built once: rebuilding them for
34
+ * every file was most of the plugin's per-file cost, as it was in flux (#513, #517).
35
+ */ const baseConfigs = new Map();
34
36
  const transformCache = new Map();
35
37
  let workspace = null;
36
38
  const initWorkspace = async ()=>{
@@ -41,10 +43,17 @@ export function forgeBuild(options) {
41
43
  rootDir: workspaceRoot
42
44
  });
43
45
  };
44
- const preWarmProject = (project)=>{
45
- if (warmedProjects.has(project.projectDirectory)) return;
46
- warmedProjects.add(project.projectDirectory);
47
- void transpiler.transformProject(project).then(()=>logger.debug(`Pre-warm complete for ${project.name}`)).catch((err)=>logger.error(`Pre-warm error for ${project.name}: ${err.message}`));
46
+ const baseConfigFor = (project)=>{
47
+ const cached = baseConfigs.get(project);
48
+ if (cached != null) return cached;
49
+ const config = transpiler.createBaseConfig(BuildTargets.es6, {
50
+ aliasPaths: true,
51
+ tsConfig: project?.getTsConfigSync(),
52
+ userConfig: project?.getSwcConfigSync(),
53
+ workspace: workspace ?? undefined
54
+ });
55
+ baseConfigs.set(project, config);
56
+ return config;
48
57
  };
49
58
  return {
50
59
  name: PLUGIN_NAME,
@@ -62,42 +71,24 @@ export function forgeBuild(options) {
62
71
  return null;
63
72
  },
64
73
  watchChange (id) {
65
- const file = new FileEx({
66
- fullPath: id
67
- });
68
74
  transformCache.delete(id);
69
- transpiler.invalidateContentCache(file);
70
75
  },
71
76
  async transform (code, id) {
72
77
  if (id.includes("node_modules") || !/[.](ts|tsx|mts|cts)$/.test(id)) {
73
78
  return null;
74
79
  }
75
- await initWorkspace();
76
- const project = await workspace?.findProject(id, buildConfig);
77
- if (project != null && !skipBuild) {
78
- preWarmProject(project);
79
- }
80
80
  const cached = transformCache.get(id);
81
81
  if (cached !== undefined && cached.content === code) {
82
82
  return cached.result;
83
83
  }
84
- const file = new FileEx({
84
+ await initWorkspace();
85
+ const project = await workspace?.findProject(id, buildConfig);
86
+ // The code Vite passes, not a read of the file: no content cache can serve an older
87
+ // version, and there is no pre-warm compiling files ahead from disk (#517).
88
+ const response = await transpiler.transformCode(code, new FileEx({
85
89
  fullPath: id,
86
90
  content: code
87
- });
88
- if (cached !== undefined) {
89
- // Content changed for a previously-seen file — invalidate the transpiler's
90
- // FILE_CONTENT cache so the fresh content from Vite is used.
91
- // The VS Code Vitest extension disables Vite's chokidar watcher
92
- // (server.watch: null) and drives re-runs via vitest.watcher.onFileChange(),
93
- // bypassing watchChange entirely — so we cannot rely on watchChange alone.
94
- transpiler.invalidateContentCache(file);
95
- }
96
- const [response] = await transpiler.transformFile(file, BuildTargets.es6, {
97
- aliasPaths: true,
98
- tsConfig: project ? await project.getTsConfig() : undefined,
99
- workspace: workspace ?? undefined
100
- });
91
+ }), BuildTargets.es6, baseConfigFor(project));
101
92
  if (!response || response.outcome !== "success") return null;
102
93
  const result = {
103
94
  code: response.code,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decohere/forge",
3
- "version": "0.0.1-dev.111",
3
+ "version": "0.0.1-dev.112",
4
4
  "description": "TypeScript monorepo build system — zero config, fast compilation, streamlined developer workflow",
5
5
  "keywords": [
6
6
  "typescript",
@@ -29,10 +29,10 @@
29
29
  "test": "echo \"Error: no test specified\" && exit 1"
30
30
  },
31
31
  "dependencies": {
32
- "@decohere/crucible": "0.0.1-dev.111",
33
- "@decohere/mesh": "0.0.1-dev.111",
34
- "@decohere/nexus": "0.0.1-dev.111",
35
- "@decohere/scribe": "0.0.1-dev.111",
32
+ "@decohere/crucible": "0.0.1-dev.112",
33
+ "@decohere/mesh": "0.0.1-dev.112",
34
+ "@decohere/nexus": "0.0.1-dev.112",
35
+ "@decohere/scribe": "0.0.1-dev.112",
36
36
  "@swc/core": "^1.11.11",
37
37
  "async": "^3.2.6",
38
38
  "parse-imports": "^2.2.1",
@@ -42,6 +42,12 @@ export declare class SwcTranspiler extends SwcTranspiler_base implements Transfo
42
42
  * runs it on libuv's thread pool, so many calls in flight compile in parallel (#661).
43
43
  */
44
44
  transformFileAsync(file: FileEx, buildTarget: TranspileTarget, options?: TranspileOptions, baseConfig?: Options): Promise<TransformResponse>;
45
+ /**
46
+ * Compiles `content` as the source of `file`, without reading the file: for callers that
47
+ * already hold the code, such as the Vitest plugin, which must compile what Vite passes it
48
+ * rather than an earlier read of the disk (#517). Async SWC, on the thread pool.
49
+ */
50
+ transformCode(content: string, file: FileEx, buildTarget: TranspileTarget, baseConfig: Options): Promise<TransformResponse>;
45
51
  private static skippedEmpty;
46
52
  private static succeeded;
47
53
  private transformTarget;
@@ -2,8 +2,6 @@ export type ForgeBuildOptions = {
2
2
  buildConfig?: string;
3
3
  logLevel?: string;
4
4
  workspaceRoot?: string;
5
- /** Skip the build pre-warm entirely (e.g. when a pre-built artifact is already on disk). */
6
- skipBuild?: boolean;
7
5
  };
8
6
  export declare function forgeBuild(options?: ForgeBuildOptions): {
9
7
  name: string;