@decohere/forge 0.0.1-dev.130 → 0.0.1-dev.131

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/README.md CHANGED
@@ -6,6 +6,8 @@ Build system (SWC). `forge build --configuration debug` rewrites workspace impor
6
6
 
7
7
  `forge build` transpiles without waiting for a whole-workspace typecheck. Run `forge typecheck` independently (including in the background) and require it before shipping. Why executable tests come before static contracts: [testing principles](../../docs/testing/#executable-behavior-before-type-contracts).
8
8
 
9
+ With no `tsconfig.json`, `forge typecheck` reads a project as plain `tsc` would, with decorators on because forge and flux compile them by default. Add a `tsconfig.json` to choose your own options; forge then limits `lib` to ES2022 and turns off `@types` discovery unless it sets them (`types: ["node"]`).
10
+
9
11
  ## Debug path aliases (not `node_modules` links)
10
12
 
11
13
  Forge debug (`aliasPaths: true`) asks the workspace package for a map of `package.json` `name` → source `main` (`Workspace.getPackageAliases`) and feeds it to SWC as `jsc.paths`. Flux uses the same map in its resolve hook only, and compiles without `jsc.paths` or `resolveFully`, so imports are resolved once.
@@ -8,9 +8,9 @@ Object.defineProperty(exports, "IncrementalWatch", {
8
8
  return IncrementalWatch;
9
9
  }
10
10
  });
11
- const _rxjs = require("rxjs");
11
+ const _system = require("@decohere/core/system");
12
12
  class IncrementalWatch {
13
- #results = new _rxjs.Subject();
13
+ #results = new _system.Subject();
14
14
  get results$() {
15
15
  return this.#results.asObservable();
16
16
  }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "tsCompilerOptions", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return tsCompilerOptions;
9
+ }
10
+ });
11
+ const _nodefs = /*#__PURE__*/ _interop_require_default(require("node:fs"));
12
+ const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
13
+ const _typescript = /*#__PURE__*/ _interop_require_default(require("typescript"));
14
+ const _errors = require("../errors.js");
15
+ function _interop_require_default(obj) {
16
+ return obj && obj.__esModule ? obj : {
17
+ default: obj
18
+ };
19
+ }
20
+ const host = {
21
+ ..._typescript.default.sys,
22
+ readFile: (file)=>_nodefs.default.readFileSync(file, {
23
+ encoding: "utf8"
24
+ }),
25
+ readDirectory: (directoryName)=>_nodefs.default.readdirSync(directoryName, {
26
+ withFileTypes: true
27
+ }).filter((entry)=>entry.isDirectory()).map((entry)=>entry.name)
28
+ };
29
+ /**
30
+ * No tsconfig means no choices made, so this reads the project as plain tsc would: it clears
31
+ * the `lib` and `types` limits forge sets to keep monorepos fast, which a project opts out of
32
+ * in its tsconfig. Decorators are on because forge and flux compile them by default.
33
+ */ const DEFAULT_TS_OPTIONS = {
34
+ emitDeclarationOnly: true,
35
+ declaration: true,
36
+ lib: undefined,
37
+ types: undefined,
38
+ experimentalDecorators: true
39
+ };
40
+ function tsCompilerOptions(tsconfig) {
41
+ const { filePath } = tsconfig;
42
+ if (filePath == null) return {
43
+ ...DEFAULT_TS_OPTIONS
44
+ };
45
+ const result = _typescript.default.parseConfigFileTextToJson(filePath, _nodefs.default.readFileSync(filePath, "utf8"));
46
+ if (result.error) {
47
+ throw new _errors.ForgeError(`Error parsing ${filePath}. ${_typescript.default.flattenDiagnosticMessageText(result.error.messageText, "\n")}`);
48
+ }
49
+ return _typescript.default.parseJsonConfigFileContent(result.config, host, _nodepath.default.dirname(filePath)).options;
50
+ }
@@ -42,7 +42,7 @@ class TypeEmissionProgram {
42
42
  this.#targetsByDtsPath = targets.byDtsPath;
43
43
  this.#targetsByEmittedPath = targets.byEmittedPath;
44
44
  this.#host = hostFactory;
45
- this.#compilerOptions = group[0].tsconfig.compilerOptions;
45
+ this.#compilerOptions = group[0].compilerOptions;
46
46
  this.#requiredOptions = requiredOptions;
47
47
  this.#allFiles = group.flatMap(({ files })=>files);
48
48
  this.#compilerHost = hostFactory.createHost(this.#compilerOptions);
@@ -28,6 +28,7 @@ const _io = require("@decohere/devkit/io");
28
28
  require("../extensions/project.extensions.js");
29
29
  const _incrementalwatch = require("./incremental-watch.js");
30
30
  const _outputcache = require("./output-cache.js");
31
+ const _tscompileroptions = require("./ts-compiler-options.js");
31
32
  const _tsprogramhost = require("./ts-program-host.js");
32
33
  const _typeemissionprogram = require("./type-emission-program.js");
33
34
  const _buildtargettype = require("../types/build-target.type.js");
@@ -121,17 +122,21 @@ class TypeEmitter {
121
122
  return [];
122
123
  }
123
124
  // Pre-fetch all files and tsconfigs in parallel across all projects
124
- const projectData = await Promise.all(projects.map(async (proj)=>({
125
+ const projectData = await Promise.all(projects.map(async (proj)=>{
126
+ const tsconfig = await proj.getTsConfig();
127
+ return {
125
128
  project: proj,
126
129
  files: await proj.getAllFiles(),
127
- tsconfig: await proj.getTsConfig()
128
- })));
130
+ tsconfig,
131
+ compilerOptions: (0, _tscompileroptions.tsCompilerOptions)(tsconfig)
132
+ };
133
+ }));
129
134
  // Group projects by the resolved compiler options that affect the compiler
130
135
  // host (lib loading, module resolution, path aliases). Projects sharing the
131
136
  // same resolved values get one shared host AND one shared program — all their
132
137
  // files are compiled together, matching v1's single-program approach which
133
138
  // avoids re-parsing lib/node_modules .d.ts files per project.
134
- const groups = Map.groupBy(projectData, ({ tsconfig })=>this.#host.hostKey(tsconfig.compilerOptions));
139
+ const groups = Map.groupBy(projectData, ({ compilerOptions })=>this.#host.hostKey(compilerOptions));
135
140
  const requiredCompilerOptions = options.requiredCompilerOptions ?? REQUIRED_TYPE_EMITTER_OPTIONS;
136
141
  return [
137
142
  ...groups.values()
@@ -0,0 +1,30 @@
1
+ // What compiling one file needs (flux, the vitest plugin), without forge's commands: those
2
+ // load the TypeScript compiler, which a plain `flux run` never uses.
3
+ "use strict";
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ function _export(target, all) {
8
+ for(var name in all)Object.defineProperty(target, name, {
9
+ enumerable: true,
10
+ get: Object.getOwnPropertyDescriptor(all, name).get
11
+ });
12
+ }
13
+ _export(exports, {
14
+ get BuildTargets () {
15
+ return _buildtargettype.BuildTargets;
16
+ },
17
+ get SwcTranspiler () {
18
+ return _swctranspiler.SwcTranspiler;
19
+ },
20
+ get createWorkspace () {
21
+ return _createworkspace.createWorkspace;
22
+ },
23
+ get transpileCache () {
24
+ return _transpilecache.transpileCache;
25
+ }
26
+ });
27
+ const _createworkspace = require("../create-workspace.js");
28
+ const _swctranspiler = require("../services/swc-transpiler.js");
29
+ const _transpilecache = require("../services/transpile-cache.js");
30
+ const _buildtargettype = require("../types/build-target.type.js");
@@ -1,4 +1,4 @@
1
- import { Subject } from "rxjs";
1
+ import { Subject } from "@decohere/core/system";
2
2
  /**
3
3
  * Runs a transformer's handler for every workspace file change and emits what it built. The
4
4
  * handler invalidates the transformer's own caches, rebuilds, and returns the responses (empty
@@ -0,0 +1,39 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import ts from "typescript";
4
+ import { ForgeError } from "../errors.js";
5
+ const host = {
6
+ ...ts.sys,
7
+ readFile: (file)=>fs.readFileSync(file, {
8
+ encoding: "utf8"
9
+ }),
10
+ readDirectory: (directoryName)=>fs.readdirSync(directoryName, {
11
+ withFileTypes: true
12
+ }).filter((entry)=>entry.isDirectory()).map((entry)=>entry.name)
13
+ };
14
+ /**
15
+ * No tsconfig means no choices made, so this reads the project as plain tsc would: it clears
16
+ * the `lib` and `types` limits forge sets to keep monorepos fast, which a project opts out of
17
+ * in its tsconfig. Decorators are on because forge and flux compile them by default.
18
+ */ const DEFAULT_TS_OPTIONS = {
19
+ emitDeclarationOnly: true,
20
+ declaration: true,
21
+ lib: undefined,
22
+ types: undefined,
23
+ experimentalDecorators: true
24
+ };
25
+ /**
26
+ * A project's compiler options as the TypeScript compiler parses its `tsconfig.json`: what
27
+ * type emission needs (enums, every option, tsc's own defaults). The workspace reads only
28
+ * what transpiling needs, without loading the compiler.
29
+ */ export function tsCompilerOptions(tsconfig) {
30
+ const { filePath } = tsconfig;
31
+ if (filePath == null) return {
32
+ ...DEFAULT_TS_OPTIONS
33
+ };
34
+ const result = ts.parseConfigFileTextToJson(filePath, fs.readFileSync(filePath, "utf8"));
35
+ if (result.error) {
36
+ throw new ForgeError(`Error parsing ${filePath}. ${ts.flattenDiagnosticMessageText(result.error.messageText, "\n")}`);
37
+ }
38
+ return ts.parseJsonConfigFileContent(result.config, host, path.dirname(filePath)).options;
39
+ }
@@ -27,7 +27,7 @@ export class TypeEmissionProgram {
27
27
  this.#targetsByDtsPath = targets.byDtsPath;
28
28
  this.#targetsByEmittedPath = targets.byEmittedPath;
29
29
  this.#host = hostFactory;
30
- this.#compilerOptions = group[0].tsconfig.compilerOptions;
30
+ this.#compilerOptions = group[0].compilerOptions;
31
31
  this.#requiredOptions = requiredOptions;
32
32
  this.#allFiles = group.flatMap(({ files })=>files);
33
33
  this.#compilerHost = hostFactory.createHost(this.#compilerOptions);
@@ -13,6 +13,7 @@ import { WatchEvents } from "@decohere/devkit/io";
13
13
  import "../extensions/project.extensions.js";
14
14
  import { IncrementalWatch } from "./incremental-watch.js";
15
15
  import { OutputCache } from "./output-cache.js";
16
+ import { tsCompilerOptions } from "./ts-compiler-options.js";
16
17
  import { TsProgramHost } from "./ts-program-host.js";
17
18
  import { TypeEmissionProgram } from "./type-emission-program.js";
18
19
  import { BuildTargets } from "../types/build-target.type.js";
@@ -111,17 +112,21 @@ export class TypeEmitter {
111
112
  return [];
112
113
  }
113
114
  // Pre-fetch all files and tsconfigs in parallel across all projects
114
- const projectData = await Promise.all(projects.map(async (proj)=>({
115
+ const projectData = await Promise.all(projects.map(async (proj)=>{
116
+ const tsconfig = await proj.getTsConfig();
117
+ return {
115
118
  project: proj,
116
119
  files: await proj.getAllFiles(),
117
- tsconfig: await proj.getTsConfig()
118
- })));
120
+ tsconfig,
121
+ compilerOptions: tsCompilerOptions(tsconfig)
122
+ };
123
+ }));
119
124
  // Group projects by the resolved compiler options that affect the compiler
120
125
  // host (lib loading, module resolution, path aliases). Projects sharing the
121
126
  // same resolved values get one shared host AND one shared program — all their
122
127
  // files are compiled together, matching v1's single-program approach which
123
128
  // avoids re-parsing lib/node_modules .d.ts files per project.
124
- const groups = Map.groupBy(projectData, ({ tsconfig })=>this.#host.hostKey(tsconfig.compilerOptions));
129
+ const groups = Map.groupBy(projectData, ({ compilerOptions })=>this.#host.hostKey(compilerOptions));
125
130
  const requiredCompilerOptions = options.requiredCompilerOptions ?? REQUIRED_TYPE_EMITTER_OPTIONS;
126
131
  return [
127
132
  ...groups.values()
@@ -0,0 +1,6 @@
1
+ // What compiling one file needs (flux, the vitest plugin), without forge's commands: those
2
+ // load the TypeScript compiler, which a plain `flux run` never uses.
3
+ export { createWorkspace } from "../create-workspace.js";
4
+ export { SwcTranspiler } from "../services/swc-transpiler.js";
5
+ export { transpileCache } from "../services/transpile-cache.js";
6
+ export { BuildTargets } from "../types/build-target.type.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decohere/forge",
3
- "version": "0.0.1-dev.130",
3
+ "version": "0.0.1-dev.131",
4
4
  "description": "TypeScript monorepo build system — zero config, fast compilation, streamlined developer workflow",
5
5
  "keywords": [
6
6
  "typescript",
@@ -28,6 +28,11 @@
28
28
  "import": "./es6/src/index.js",
29
29
  "require": "./commonjs/src/index.js"
30
30
  },
31
+ "./transpile": {
32
+ "types": "./types/src/transpile/index.d.ts",
33
+ "import": "./es6/src/transpile/index.js",
34
+ "require": "./commonjs/src/transpile/index.js"
35
+ },
31
36
  "./vitest": {
32
37
  "types": "./types/src/vitest/index.d.ts",
33
38
  "import": "./es6/src/vitest/index.js",
@@ -38,15 +43,14 @@
38
43
  "test": "echo \"Error: no test specified\" && exit 1"
39
44
  },
40
45
  "dependencies": {
41
- "@decohere/core": "0.0.1-dev.130",
42
- "@decohere/crucible": "0.0.1-dev.130",
43
- "@decohere/devkit": "0.0.1-dev.130",
44
- "@decohere/scribe": "0.0.1-dev.130",
45
- "@decohere/workspace": "0.0.1-dev.130",
46
+ "@decohere/core": "0.0.1-dev.131",
47
+ "@decohere/crucible": "0.0.1-dev.131",
48
+ "@decohere/devkit": "0.0.1-dev.131",
49
+ "@decohere/scribe": "0.0.1-dev.131",
50
+ "@decohere/workspace": "0.0.1-dev.131",
46
51
  "@swc/core": "^1.11.11",
47
52
  "async": "^3.2.6",
48
53
  "parse-imports": "^2.2.1",
49
- "rxjs": "^7.8.2",
50
54
  "typescript": "^6.0.3"
51
55
  },
52
56
  "devDependencies": {}
@@ -1,5 +1,5 @@
1
1
  import type { ProjectFileEvent, Workspace } from "@decohere/workspace";
2
- import type { Observable } from "rxjs";
2
+ import type { Observable } from "@decohere/core/system";
3
3
  import type { TransformResponse } from "../types/transform-response.model.js";
4
4
  /**
5
5
  * Runs a transformer's handler for every workspace file change and emits what it built. The
@@ -1,7 +1,7 @@
1
1
  import { type Project, type Tsconfig, type Workspace } from "@decohere/workspace";
2
2
  import type { FileEx } from "@decohere/core/io";
3
3
  import { type Options } from "@swc/core";
4
- import type { Observable } from "rxjs";
4
+ import type { Observable } from "@decohere/core/system";
5
5
  import { type BuildTarget, type TranspileTarget } from "../types/build-target.type.js";
6
6
  import "../extensions/project.extensions.js";
7
7
  import type { SwcConfig } from "../types/swc-config.type.js";
@@ -0,0 +1,8 @@
1
+ import type { Tsconfig } from "@decohere/workspace";
2
+ import ts from "typescript";
3
+ /**
4
+ * A project's compiler options as the TypeScript compiler parses its `tsconfig.json`: what
5
+ * type emission needs (enums, every option, tsc's own defaults). The workspace reads only
6
+ * what transpiling needs, without loading the compiler.
7
+ */
8
+ export declare function tsCompilerOptions(tsconfig: Tsconfig): ts.CompilerOptions;
@@ -7,6 +7,8 @@ export type TypeProgramProject = {
7
7
  project: Project;
8
8
  files: FileEx[];
9
9
  tsconfig: Tsconfig;
10
+ /** The compiler's parse of `tsconfig`. */
11
+ compilerOptions: ts.CompilerOptions;
10
12
  };
11
13
  export declare class TypeEmissionProgram {
12
14
  #private;
@@ -1,6 +1,6 @@
1
1
  import type { Project, Workspace } from "@decohere/workspace";
2
2
  import type { FileEx } from "@decohere/core/io";
3
- import type { Observable } from "rxjs";
3
+ import type { Observable } from "@decohere/core/system";
4
4
  import type ts from "typescript";
5
5
  import "../extensions/project.extensions.js";
6
6
  import { type BuildTarget } from "../types/build-target.type.js";
@@ -0,0 +1,6 @@
1
+ export { createWorkspace } from "../create-workspace.js";
2
+ export type { TranspileOptions } from "../services/swc-transpiler.js";
3
+ export { SwcTranspiler } from "../services/swc-transpiler.js";
4
+ export { transpileCache } from "../services/transpile-cache.js";
5
+ export type { BuildTarget, TranspileTarget } from "../types/build-target.type.js";
6
+ export { BuildTargets } from "../types/build-target.type.js";
@@ -1,6 +1,6 @@
1
1
  import type { Project, Workspace } from "@decohere/workspace";
2
2
  import type { FileEx } from "@decohere/core/io";
3
- import type { Observable } from "rxjs";
3
+ import type { Observable } from "@decohere/core/system";
4
4
  import type { BuildTarget } from "../types/build-target.type.js";
5
5
  import type { TransformResponse } from "./transform-response.model.js";
6
6
  export declare const TransformerKey: unique symbol;