@decohere/workspace 0.0.1-dev.116

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 (53) hide show
  1. package/README.md +81 -0
  2. package/commonjs/src/const.js +69 -0
  3. package/commonjs/src/errors.js +12 -0
  4. package/commonjs/src/factories/config.factory.js +90 -0
  5. package/commonjs/src/factories/find-workspace-root.js +37 -0
  6. package/commonjs/src/factories/project.factory.js +72 -0
  7. package/commonjs/src/factories/workspace.factory.js +50 -0
  8. package/commonjs/src/index.js +74 -0
  9. package/commonjs/src/models/has-package-json.mixin.js +50 -0
  10. package/commonjs/src/models/has-tsconfig.mixin.js +75 -0
  11. package/commonjs/src/models/package-aliases.js +112 -0
  12. package/commonjs/src/models/package-json.model.js +196 -0
  13. package/commonjs/src/models/project-file-event.js +4 -0
  14. package/commonjs/src/models/project.model.js +316 -0
  15. package/commonjs/src/models/source-extensions.js +81 -0
  16. package/commonjs/src/models/tsconfig.model.js +97 -0
  17. package/commonjs/src/models/workspace-config.type.js +7 -0
  18. package/commonjs/src/models/workspace.model.js +153 -0
  19. package/es6/src/const.js +36 -0
  20. package/es6/src/errors.js +2 -0
  21. package/es6/src/factories/config.factory.js +34 -0
  22. package/es6/src/factories/find-workspace-root.js +26 -0
  23. package/es6/src/factories/project.factory.js +57 -0
  24. package/es6/src/factories/workspace.factory.js +35 -0
  25. package/es6/src/index.js +14 -0
  26. package/es6/src/models/has-package-json.mixin.js +35 -0
  27. package/es6/src/models/has-tsconfig.mixin.js +60 -0
  28. package/es6/src/models/package-aliases.js +97 -0
  29. package/es6/src/models/package-json.model.js +186 -0
  30. package/es6/src/models/project-file-event.js +1 -0
  31. package/es6/src/models/project.model.js +301 -0
  32. package/es6/src/models/source-extensions.js +66 -0
  33. package/es6/src/models/tsconfig.model.js +74 -0
  34. package/es6/src/models/workspace-config.type.js +4 -0
  35. package/es6/src/models/workspace.model.js +143 -0
  36. package/package.json +34 -0
  37. package/types/src/const.d.ts +8 -0
  38. package/types/src/errors.d.ts +2 -0
  39. package/types/src/factories/config.factory.d.ts +6 -0
  40. package/types/src/factories/find-workspace-root.d.ts +6 -0
  41. package/types/src/factories/project.factory.d.ts +8 -0
  42. package/types/src/factories/workspace.factory.d.ts +9 -0
  43. package/types/src/index.d.ts +18 -0
  44. package/types/src/models/has-package-json.mixin.d.ts +9 -0
  45. package/types/src/models/has-tsconfig.mixin.d.ts +11 -0
  46. package/types/src/models/package-aliases.d.ts +28 -0
  47. package/types/src/models/package-json.model.d.ts +39 -0
  48. package/types/src/models/project-file-event.d.ts +8 -0
  49. package/types/src/models/project.model.d.ts +64 -0
  50. package/types/src/models/source-extensions.d.ts +22 -0
  51. package/types/src/models/tsconfig.model.d.ts +11 -0
  52. package/types/src/models/workspace-config.type.d.ts +52 -0
  53. package/types/src/models/workspace.model.d.ts +40 -0
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "sourceExtensions", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return sourceExtensions;
9
+ }
10
+ });
11
+ const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
12
+ function _interop_require_default(obj) {
13
+ return obj && obj.__esModule ? obj : {
14
+ default: obj
15
+ };
16
+ }
17
+ /** TypeScript source extensions a workspace compiles (#518). */ const SOURCE = [
18
+ ".ts",
19
+ ".tsx",
20
+ ".mts",
21
+ ".cts"
22
+ ];
23
+ /** The sources an import's extension can name: `./a.js` is `a.ts` or `a.tsx`, `./a.mjs` is `a.mts`. */ const SOURCES_FOR = {
24
+ ".js": [
25
+ ".ts",
26
+ ".tsx"
27
+ ],
28
+ ".jsx": [
29
+ ".tsx"
30
+ ],
31
+ ".mjs": [
32
+ ".mts"
33
+ ],
34
+ ".cjs": [
35
+ ".cts"
36
+ ],
37
+ "": [
38
+ ".ts",
39
+ ".tsx"
40
+ ]
41
+ };
42
+ /** Whether a path is a TypeScript source (not a declaration file). */ function isSource(file) {
43
+ return SOURCE.some((ext)=>file.endsWith(ext)) && !/\.d\.[mc]?ts$/.test(file);
44
+ }
45
+ /**
46
+ * The source files a path written in an import (or a built path) could name, most likely first:
47
+ * itself when it is already a source, else the same stem with each matching source extension.
48
+ */ function candidates(file) {
49
+ if (isSource(file)) return [
50
+ file
51
+ ];
52
+ const ext = _nodepath.default.extname(file);
53
+ const known = SOURCES_FOR[ext];
54
+ if (known == null) return [
55
+ `${file}.ts`,
56
+ `${file}.tsx`
57
+ ];
58
+ const stem = ext === "" ? file : file.slice(0, -ext.length);
59
+ return known.map((sourceExt)=>`${stem}${sourceExt}`);
60
+ }
61
+ /** The extension a source builds to: `.mts` to `.mjs`, `.cts` to `.cjs`, the rest to `.js`. */ function outputExtension(file) {
62
+ if (file.endsWith(".mts")) return ".mjs";
63
+ if (file.endsWith(".cts")) return ".cjs";
64
+ return ".js";
65
+ }
66
+ /** The path a source builds to: `a.mts` to `a.mjs`, `a.cts` to `a.cjs`, `a.ts` and `a.tsx` to `a.js`. */ function outputPath(file) {
67
+ const ext = _nodepath.default.extname(file);
68
+ const stem = ext === "" ? file : file.slice(0, -ext.length);
69
+ return `${stem}${outputExtension(file)}`;
70
+ }
71
+ /** Whether a source is CommonJS (`.cts`); everything else compiles as an ES module. */ function isCommonJs(file) {
72
+ return file.endsWith(".cts");
73
+ }
74
+ const sourceExtensions = {
75
+ SOURCE,
76
+ isSource,
77
+ candidates,
78
+ outputExtension,
79
+ outputPath,
80
+ isCommonJs
81
+ };
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get DEFAULT_COMPILER_OPTIONS () {
13
+ return DEFAULT_COMPILER_OPTIONS;
14
+ },
15
+ get Tsconfig () {
16
+ return Tsconfig;
17
+ }
18
+ });
19
+ const _nodefs = /*#__PURE__*/ _interop_require_default(require("node:fs"));
20
+ const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
21
+ const _io = require("@decohere/core/io");
22
+ const _typescript = /*#__PURE__*/ _interop_require_default(require("typescript"));
23
+ const _errors = require("../errors.js");
24
+ function _interop_require_default(obj) {
25
+ return obj && obj.__esModule ? obj : {
26
+ default: obj
27
+ };
28
+ }
29
+ const host = _typescript.default.sys;
30
+ host.readFile = (file)=>{
31
+ return _nodefs.default.readFileSync(file, {
32
+ encoding: "utf8"
33
+ });
34
+ };
35
+ host.readDirectory = (directoryName)=>{
36
+ return _nodefs.default.readdirSync(directoryName, {
37
+ withFileTypes: true
38
+ }).filter((entry)=>entry.isDirectory()).map((entry)=>entry.name);
39
+ };
40
+ const DEFAULT_COMPILER_OPTIONS = {
41
+ emitDeclarationOnly: true,
42
+ declaration: true,
43
+ baseUrl: "./"
44
+ };
45
+ class Tsconfig {
46
+ static default() {
47
+ return new Tsconfig();
48
+ }
49
+ static async parse(filePath) {
50
+ if (!await _io.io.fileExists(filePath)) {
51
+ throw new _errors.WorkspaceConfigurationError(`TSConfig not found ${filePath}`);
52
+ }
53
+ const configFileText = await _io.io.readFile(filePath);
54
+ const result = _typescript.default.parseConfigFileTextToJson(filePath, configFileText);
55
+ if (result.error) {
56
+ const errorMessage = result.error.messageText.toString();
57
+ throw new _errors.WorkspaceConfigurationError(`Error parsing ${filePath}. ${errorMessage}`);
58
+ }
59
+ const dirName = _nodepath.default.resolve(_nodepath.default.dirname(filePath));
60
+ const configParseResult = _typescript.default.parseJsonConfigFileContent(result.config, host, dirName);
61
+ return new Tsconfig({
62
+ compilerOptions: configParseResult.options,
63
+ errors: configParseResult.errors
64
+ });
65
+ }
66
+ static parseSync(filePath) {
67
+ if (!Tsconfig.fileExistsSync(filePath)) {
68
+ throw new _errors.WorkspaceConfigurationError(`TSConfig not found ${filePath}`);
69
+ }
70
+ const configFileText = _nodefs.default.readFileSync(filePath, "utf8");
71
+ const result = _typescript.default.parseConfigFileTextToJson(filePath, configFileText);
72
+ if (result.error) {
73
+ const errorMessage = result.error.messageText.toString();
74
+ throw new _errors.WorkspaceConfigurationError(`Error parsing ${filePath}. ${errorMessage}`);
75
+ }
76
+ const dirName = _nodepath.default.resolve(_nodepath.default.dirname(filePath));
77
+ const configParseResult = _typescript.default.parseJsonConfigFileContent(result.config, host, dirName);
78
+ return new Tsconfig({
79
+ compilerOptions: configParseResult.options,
80
+ errors: configParseResult.errors
81
+ });
82
+ }
83
+ static fileExistsSync(filePath) {
84
+ try {
85
+ return _nodefs.default.statSync(filePath).isFile();
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+ constructor(data = {
91
+ compilerOptions: DEFAULT_COMPILER_OPTIONS
92
+ }){
93
+ Object.assign(this, data);
94
+ }
95
+ compilerOptions;
96
+ errors;
97
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Generic configuration for a workspace build profile.
3
+ * Concrete tools (e.g. @decohere/forge) extend this with tool-specific fields.
4
+ */ "use strict";
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "Workspace", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return Workspace;
9
+ }
10
+ });
11
+ const _decorators = require("@decohere/crucible/decorators");
12
+ const _collections = require("@decohere/core/collections");
13
+ const _extensibility = require("@decohere/core/extensibility");
14
+ const _rxjs = require("rxjs");
15
+ const _errors = require("../errors.js");
16
+ const _haspackagejsonmixin = require("./has-package-json.mixin.js");
17
+ const _hastsconfigmixin = require("./has-tsconfig.mixin.js");
18
+ const _packagealiases = require("./package-aliases.js");
19
+ const _packagejsonmodel = require("./package-json.model.js");
20
+ const _projectmodel = require("./project.model.js");
21
+ function _ts_decorate(decorators, target, key, desc) {
22
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
23
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
24
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
25
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
26
+ }
27
+ function _ts_metadata(k, v) {
28
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
29
+ }
30
+ class Workspace extends (0, _extensibility.mixwith)(_haspackagejsonmixin.HasPackageJson, _hastsconfigmixin.HasTsConfig) {
31
+ packageJson;
32
+ #name;
33
+ #config;
34
+ #projectSeeds = [];
35
+ #projectCache = new Map();
36
+ /**
37
+ * Each project's source file paths as a set, so `findProject` is a lookup rather than a scan
38
+ * of every file: the Vitest plugin asks once per transformed file (#517). Keyed by the list
39
+ * `getAllFiles` returns, so a new list (after its cache is flushed) gets a new set.
40
+ */ #sourcePaths = new WeakMap();
41
+ #projectFileChangedSubject = new _rxjs.Subject();
42
+ #projectWatchSubscriptions = new Map();
43
+ #watchedProjects = [];
44
+ constructor(options = {}){
45
+ super();
46
+ const { packageJson, config, projects } = options;
47
+ this.#name = packageJson?.name ?? "WORKSPACE";
48
+ this.packageJson = packageJson ?? new _packagejsonmodel.PackageJson();
49
+ this.#config = config ?? {};
50
+ this.#projectSeeds = projects ?? [];
51
+ this.validateWorkspace();
52
+ }
53
+ get name() {
54
+ return this.#name;
55
+ }
56
+ get projectSeeds() {
57
+ return this.#projectSeeds;
58
+ }
59
+ getProjects(configuration) {
60
+ const cached = this.#projectCache.get(configuration);
61
+ if (cached != null) {
62
+ return cached;
63
+ }
64
+ const projects = this.#projectSeeds.map((seed)=>new _projectmodel.Project(seed.name, seed.config ?? {}, configuration));
65
+ this.#projectCache.set(configuration, projects);
66
+ return projects;
67
+ }
68
+ get rootDir() {
69
+ return this.#config.rootDir ?? "./";
70
+ }
71
+ get packageJsonDirectory() {
72
+ return this.rootDir;
73
+ }
74
+ get tsConfigDirectory() {
75
+ return this.rootDir;
76
+ }
77
+ get fallbackTsConfigDirectory() {
78
+ return undefined;
79
+ }
80
+ /**
81
+ * Package-name aliases for this configuration: workspace convention, then
82
+ * workspace `tsconfig.json` `paths` overrides. Forge and Flux must use this
83
+ * instead of assembling the map themselves.
84
+ */ getPackageAliases(configuration) {
85
+ const tsconfig = this.findTsConfigPath() != null ? this.getTsConfigSync() : undefined;
86
+ const baseUrl = typeof tsconfig?.compilerOptions?.baseUrl === "string" && tsconfig.compilerOptions.baseUrl.length > 0 ? tsconfig.compilerOptions.baseUrl : this.rootDir;
87
+ return _packagealiases.packageAliases.build(this.getProjects(configuration), {
88
+ baseUrl,
89
+ tsconfigPaths: tsconfig?.compilerOptions?.paths
90
+ });
91
+ }
92
+ async watch(configuration) {
93
+ this.stopWatching();
94
+ const projects = this.getProjects(configuration);
95
+ this.#watchedProjects = projects;
96
+ for (const project of projects){
97
+ await project.watch(configuration);
98
+ const subscription = project.projectFileChanged$.subscribe((evt)=>this.#projectFileChangedSubject.next(evt));
99
+ this.#projectWatchSubscriptions.set(project.projectDirectory, subscription);
100
+ }
101
+ }
102
+ stopWatching() {
103
+ for (const subscription of this.#projectWatchSubscriptions.values()){
104
+ subscription.unsubscribe();
105
+ }
106
+ for (const project of this.#watchedProjects){
107
+ project.stopWatching();
108
+ }
109
+ this.#watchedProjects = [];
110
+ this.#projectWatchSubscriptions.clear();
111
+ }
112
+ [Symbol.dispose]() {
113
+ this.stopWatching();
114
+ }
115
+ get projectFileChanged$() {
116
+ return this.#projectFileChangedSubject.asObservable();
117
+ }
118
+ async findProject(filePath, buildConfig) {
119
+ for (const project of this.getProjects(buildConfig)){
120
+ if ((await this.sourcePathsOf(project)).has(filePath)) {
121
+ return project;
122
+ }
123
+ }
124
+ return undefined;
125
+ }
126
+ async sourcePathsOf(project) {
127
+ const files = await project.getAllFiles();
128
+ const cached = this.#sourcePaths.get(files);
129
+ if (cached != null) return cached;
130
+ const paths = new Set(files.map((file)=>file.fullPath));
131
+ this.#sourcePaths.set(files, paths);
132
+ return paths;
133
+ }
134
+ validateWorkspace() {
135
+ const errors = this.findDuplicateProjectSeeds(this.#projectSeeds);
136
+ if (errors.length > 0) {
137
+ throw new _errors.WorkspaceConfigurationError(errors.join("\n"));
138
+ }
139
+ }
140
+ findDuplicateProjectSeeds(seeds) {
141
+ const duplicates = _collections.collections.findDuplicatesByKey([
142
+ ...seeds
143
+ ], "name");
144
+ return duplicates.map(([name, group])=>`Duplicate project name "${name}" found at: ${group.map((seed)=>seed.config?.projectDir ?? name).join(", ")}`);
145
+ }
146
+ }
147
+ Workspace = _ts_decorate([
148
+ (0, _decorators.injectable)(),
149
+ _ts_metadata("design:type", Function),
150
+ _ts_metadata("design:paramtypes", [
151
+ typeof Partial === "undefined" ? Object : Partial
152
+ ])
153
+ ], Workspace);
@@ -0,0 +1,36 @@
1
+ export const TS_CONFIG = "tsconfig.json";
2
+ export const PACKAGE_JSON = "package.json";
3
+ export const NODE_MODULES = "node_modules";
4
+ export const FIXTURES = "__fixtures__";
5
+ export const WORKSPACE_CONFIG = ".decohere.js";
6
+ export const PROJECTS_DIR = "projects";
7
+ export const WorkspaceDefaults = {
8
+ rootDir: "./",
9
+ outDir: "dist",
10
+ includeProjects: [
11
+ `${PROJECTS_DIR}/**/${PACKAGE_JSON}`
12
+ ],
13
+ ignoreProjects: [
14
+ `**/${NODE_MODULES}/**`,
15
+ `**/${FIXTURES}/**`,
16
+ "**/dist/**"
17
+ ],
18
+ include: [
19
+ "**/*.ts",
20
+ "**/*.tsx",
21
+ "**/*.mts",
22
+ "**/*.cts"
23
+ ],
24
+ ignore: [
25
+ "**/*.d.ts",
26
+ "**/*.d.mts",
27
+ "**/*.d.cts",
28
+ `**/${NODE_MODULES}/**`
29
+ ],
30
+ copyFiles: [],
31
+ configurations: [
32
+ {
33
+ name: "debug"
34
+ }
35
+ ]
36
+ };
@@ -0,0 +1,2 @@
1
+ export class WorkspaceConfigurationError extends Error {
2
+ }
@@ -0,0 +1,34 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ function _ts_metadata(k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ }
10
+ import path from "node:path";
11
+ import { pathToFileURL } from "node:url";
12
+ import { cache } from "@decohere/core/caching";
13
+ import { io } from "@decohere/core/io";
14
+ import { WORKSPACE_CONFIG } from "../const.js";
15
+ class ConfigFactory {
16
+ async getUserConfig(configFile) {
17
+ const file = configFile ?? WORKSPACE_CONFIG;
18
+ if (!file || !await io.fileExists(file)) {
19
+ return {};
20
+ }
21
+ const fileUrl = pathToFileURL(path.resolve(file)).href;
22
+ const conf = (await import(fileUrl)).default;
23
+ return conf;
24
+ }
25
+ }
26
+ _ts_decorate([
27
+ cache(),
28
+ _ts_metadata("design:type", Function),
29
+ _ts_metadata("design:paramtypes", [
30
+ String
31
+ ]),
32
+ _ts_metadata("design:returntype", Promise)
33
+ ], ConfigFactory.prototype, "getUserConfig", null);
34
+ export const UserConfigFactory = new ConfigFactory();
@@ -0,0 +1,26 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { PACKAGE_JSON, PROJECTS_DIR } from "../const.js";
4
+ /**
5
+ * The workspace root for a command run from `startDir`: the nearest directory at or above
6
+ * it with a `package.json` and a `projects/` folder. Falls back to `startDir`, so running
7
+ * from a package directory (`projects/api`) still sees the whole workspace.
8
+ */ export function findWorkspaceRoot(startDir) {
9
+ const start = path.resolve(startDir);
10
+ let dir = start;
11
+ for(;;){
12
+ if (existsSync(path.join(dir, PACKAGE_JSON)) && isDirectory(path.join(dir, PROJECTS_DIR))) {
13
+ return dir;
14
+ }
15
+ const parent = path.dirname(dir);
16
+ if (parent === dir) return start;
17
+ dir = parent;
18
+ }
19
+ }
20
+ function isDirectory(fullPath) {
21
+ try {
22
+ return statSync(fullPath).isDirectory();
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
@@ -0,0 +1,57 @@
1
+ import path from "node:path";
2
+ import { io } from "@decohere/core/io";
3
+ import { merge } from "@decohere/core/objects";
4
+ import { WorkspaceConfigurationError } from "../errors.js";
5
+ import { PackageJson } from "../models/package-json.model.js";
6
+ import { Project } from "../models/project.model.js";
7
+ export class ProjectFactory {
8
+ static async getProjectSeeds(config) {
9
+ const { includeProjects, ignoreProjects, rootDir } = config;
10
+ const cwd = rootDir ?? "./";
11
+ const discoveredPackageJsonFiles = await io.findFiles(includeProjects, {
12
+ cwd,
13
+ ignore: ignoreProjects
14
+ });
15
+ const packageJsonFiles = ProjectFactory.filterNestedPackageJsonFiles(discoveredPackageJsonFiles);
16
+ const projects = await Promise.all(packageJsonFiles.map((file)=>ProjectFactory.projectFrom(file, config)));
17
+ return projects.sort((a, b)=>a.name.localeCompare(b.name));
18
+ }
19
+ static filterNestedPackageJsonFiles(files) {
20
+ const normalize = (filePath)=>filePath.replaceAll("\\", "/");
21
+ const packageJsonSet = new Set(files.map((file)=>normalize(file.relativePath)));
22
+ return files.filter((file)=>{
23
+ const relativePath = normalize(file.relativePath);
24
+ const projectDir = path.posix.dirname(relativePath);
25
+ let dir = path.posix.dirname(projectDir);
26
+ while(dir !== "." && dir !== "/"){
27
+ if (packageJsonSet.has(`${dir}/package.json`)) {
28
+ return false;
29
+ }
30
+ dir = path.posix.dirname(dir);
31
+ }
32
+ return true;
33
+ });
34
+ }
35
+ static async getProjects(config, configuration) {
36
+ const seeds = await ProjectFactory.getProjectSeeds(config);
37
+ return seeds.map((seed)=>new Project(seed.name, seed.config, configuration));
38
+ }
39
+ static async projectFrom(file, config) {
40
+ const packageJsonData = await io.readJsonFile(file.fullPath).catch((error)=>{
41
+ throw new WorkspaceConfigurationError(`Error reading package.json ${file.fullPath}`, error);
42
+ });
43
+ const name = packageJsonData.name;
44
+ const packageJson = new PackageJson(packageJsonData);
45
+ const projectDir = path.dirname(file.relativePath);
46
+ const mergedConfig = merge.withOptions({
47
+ mergeArrays: false
48
+ }, config, config?.projects?.[name] ?? {}, {
49
+ projectDir,
50
+ packageJson
51
+ });
52
+ return {
53
+ name,
54
+ config: mergedConfig
55
+ };
56
+ }
57
+ }
@@ -0,0 +1,35 @@
1
+ import path from "node:path";
2
+ import { io } from "@decohere/core/io";
3
+ import { merge } from "@decohere/core/objects";
4
+ import { PACKAGE_JSON, WorkspaceDefaults } from "../const.js";
5
+ import { PackageJson } from "../models/package-json.model.js";
6
+ import { Workspace } from "../models/workspace.model.js";
7
+ import { UserConfigFactory } from "./config.factory.js";
8
+ import { ProjectFactory } from "./project.factory.js";
9
+ class Factory {
10
+ async create(options = {}) {
11
+ const config = await this.getMergedConfig(options);
12
+ const projectSeeds = await ProjectFactory.getProjectSeeds(config);
13
+ const packageJson = await this.getPackageJson(config.rootDir ?? "./");
14
+ const workspace = new Workspace({
15
+ packageJson,
16
+ config,
17
+ projects: projectSeeds
18
+ });
19
+ return workspace;
20
+ }
21
+ async getMergedConfig(options) {
22
+ const { configFile, ...rest } = options;
23
+ const userConfig = await UserConfigFactory.getUserConfig(configFile);
24
+ return merge.withOptions({
25
+ mergeArrays: false
26
+ }, WorkspaceDefaults, userConfig, rest);
27
+ }
28
+ async getPackageJson(rootDir) {
29
+ const packageJsonPath = path.join(rootDir, PACKAGE_JSON);
30
+ if (!await io.fileExists(packageJsonPath)) return;
31
+ const data = await io.readJsonFile(packageJsonPath);
32
+ return new PackageJson(data);
33
+ }
34
+ }
35
+ export const WorkspaceFactory = new Factory();
@@ -0,0 +1,14 @@
1
+ export { WORKSPACE_CONFIG, WorkspaceDefaults } from "./const.js";
2
+ export { WorkspaceConfigurationError } from "./errors.js";
3
+ export { UserConfigFactory } from "./factories/config.factory.js";
4
+ export { findWorkspaceRoot } from "./factories/find-workspace-root.js";
5
+ export { ProjectFactory } from "./factories/project.factory.js";
6
+ export { WorkspaceFactory } from "./factories/workspace.factory.js";
7
+ export { HasPackageJson } from "./models/has-package-json.mixin.js";
8
+ export { HasTsConfig } from "./models/has-tsconfig.mixin.js";
9
+ export { packageAliases } from "./models/package-aliases.js";
10
+ export { PackageJson } from "./models/package-json.model.js";
11
+ export { Project } from "./models/project.model.js";
12
+ export { sourceExtensions } from "./models/source-extensions.js";
13
+ export { DEFAULT_COMPILER_OPTIONS, Tsconfig } from "./models/tsconfig.model.js";
14
+ export { Workspace } from "./models/workspace.model.js";
@@ -0,0 +1,35 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ function _ts_metadata(k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ }
10
+ import path from "node:path";
11
+ import { invalidateMemoize, memoize } from "@decohere/core/caching";
12
+ import { io } from "@decohere/core/io";
13
+ import { PACKAGE_JSON } from "../const.js";
14
+ import { PackageJson } from "./package-json.model.js";
15
+ export class HasPackageJson {
16
+ async getPackageJson() {
17
+ return new PackageJson(await io.readJsonFile(path.join(this.packageJsonDirectory, PACKAGE_JSON)));
18
+ }
19
+ get packageJsonPath() {
20
+ return path.join(this.packageJsonDirectory, PACKAGE_JSON);
21
+ }
22
+ async getVersion() {
23
+ return (await this.getPackageJson()).version;
24
+ }
25
+ async persistPackageJson(packageJson) {
26
+ await io.writeFile(this.packageJsonPath, packageJson.toJSON());
27
+ invalidateMemoize.evict(this, HasPackageJson.prototype.getPackageJson);
28
+ }
29
+ }
30
+ _ts_decorate([
31
+ memoize(),
32
+ _ts_metadata("design:type", Function),
33
+ _ts_metadata("design:paramtypes", []),
34
+ _ts_metadata("design:returntype", Promise)
35
+ ], HasPackageJson.prototype, "getPackageJson", null);
@@ -0,0 +1,60 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ function _ts_metadata(k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ }
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ import { memoize } from "@decohere/core/caching";
13
+ import { io } from "@decohere/core/io";
14
+ import { TS_CONFIG } from "../const.js";
15
+ import { Tsconfig } from "./tsconfig.model.js";
16
+ export class HasTsConfig {
17
+ async getTsConfig() {
18
+ for (const configPath of this.tsConfigPaths){
19
+ if (await io.fileExists(configPath)) return Tsconfig.parse(configPath);
20
+ }
21
+ return Tsconfig.default();
22
+ }
23
+ getTsConfigSync() {
24
+ const configPath = this.findTsConfigPath();
25
+ if (configPath != null) return Tsconfig.parseSync(configPath);
26
+ return Tsconfig.default();
27
+ }
28
+ /** First existing `tsconfig.json` on this object, if any. */ findTsConfigPath() {
29
+ return this.tsConfigPaths.find((configPath)=>HasTsConfig.fileExistsSync(configPath));
30
+ }
31
+ get tsConfigPaths() {
32
+ const paths = [
33
+ path.join(this.tsConfigDirectory, TS_CONFIG)
34
+ ];
35
+ const fallback = this.fallbackTsConfigDirectory;
36
+ if (fallback != null) {
37
+ paths.push(path.join(fallback, TS_CONFIG));
38
+ }
39
+ return paths;
40
+ }
41
+ static fileExistsSync(filePath) {
42
+ try {
43
+ return fs.statSync(filePath).isFile();
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+ }
49
+ _ts_decorate([
50
+ memoize(),
51
+ _ts_metadata("design:type", Function),
52
+ _ts_metadata("design:paramtypes", []),
53
+ _ts_metadata("design:returntype", Promise)
54
+ ], HasTsConfig.prototype, "getTsConfig", null);
55
+ _ts_decorate([
56
+ memoize(),
57
+ _ts_metadata("design:type", Function),
58
+ _ts_metadata("design:paramtypes", []),
59
+ _ts_metadata("design:returntype", void 0)
60
+ ], HasTsConfig.prototype, "getTsConfigSync", null);