@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.
- package/README.md +81 -0
- package/commonjs/src/const.js +69 -0
- package/commonjs/src/errors.js +12 -0
- package/commonjs/src/factories/config.factory.js +90 -0
- package/commonjs/src/factories/find-workspace-root.js +37 -0
- package/commonjs/src/factories/project.factory.js +72 -0
- package/commonjs/src/factories/workspace.factory.js +50 -0
- package/commonjs/src/index.js +74 -0
- package/commonjs/src/models/has-package-json.mixin.js +50 -0
- package/commonjs/src/models/has-tsconfig.mixin.js +75 -0
- package/commonjs/src/models/package-aliases.js +112 -0
- package/commonjs/src/models/package-json.model.js +196 -0
- package/commonjs/src/models/project-file-event.js +4 -0
- package/commonjs/src/models/project.model.js +316 -0
- package/commonjs/src/models/source-extensions.js +81 -0
- package/commonjs/src/models/tsconfig.model.js +97 -0
- package/commonjs/src/models/workspace-config.type.js +7 -0
- package/commonjs/src/models/workspace.model.js +153 -0
- package/es6/src/const.js +36 -0
- package/es6/src/errors.js +2 -0
- package/es6/src/factories/config.factory.js +34 -0
- package/es6/src/factories/find-workspace-root.js +26 -0
- package/es6/src/factories/project.factory.js +57 -0
- package/es6/src/factories/workspace.factory.js +35 -0
- package/es6/src/index.js +14 -0
- package/es6/src/models/has-package-json.mixin.js +35 -0
- package/es6/src/models/has-tsconfig.mixin.js +60 -0
- package/es6/src/models/package-aliases.js +97 -0
- package/es6/src/models/package-json.model.js +186 -0
- package/es6/src/models/project-file-event.js +1 -0
- package/es6/src/models/project.model.js +301 -0
- package/es6/src/models/source-extensions.js +66 -0
- package/es6/src/models/tsconfig.model.js +74 -0
- package/es6/src/models/workspace-config.type.js +4 -0
- package/es6/src/models/workspace.model.js +143 -0
- package/package.json +34 -0
- package/types/src/const.d.ts +8 -0
- package/types/src/errors.d.ts +2 -0
- package/types/src/factories/config.factory.d.ts +6 -0
- package/types/src/factories/find-workspace-root.d.ts +6 -0
- package/types/src/factories/project.factory.d.ts +8 -0
- package/types/src/factories/workspace.factory.d.ts +9 -0
- package/types/src/index.d.ts +18 -0
- package/types/src/models/has-package-json.mixin.d.ts +9 -0
- package/types/src/models/has-tsconfig.mixin.d.ts +11 -0
- package/types/src/models/package-aliases.d.ts +28 -0
- package/types/src/models/package-json.model.d.ts +39 -0
- package/types/src/models/project-file-event.d.ts +8 -0
- package/types/src/models/project.model.d.ts +64 -0
- package/types/src/models/source-extensions.d.ts +22 -0
- package/types/src/models/tsconfig.model.d.ts +11 -0
- package/types/src/models/workspace-config.type.d.ts +52 -0
- 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,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);
|
package/es6/src/const.js
ADDED
|
@@ -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,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();
|
package/es6/src/index.js
ADDED
|
@@ -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);
|