@decohere/workspace 0.0.1-dev.128 → 0.0.1-dev.130

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
@@ -4,10 +4,12 @@ Workspace and project model for Decohere tooling. Forge and Flux call it to find
4
4
 
5
5
  ## Workspace layout
6
6
 
7
- A **project** is a directory whose root contains a `package.json`. The workspace package discovers those files with `includeProjects` globs (cwd = the workspace root, usually `process.cwd()`).
7
+ A **project** is a directory whose root contains a `package.json`. The workspace package discovers those files with `includeProjects` globs from the workspace root: the folder the tool runs in. Nothing searches the folders above it; to run from elsewhere, pass `--root-dir <dir>` to `forge` or `flux`.
8
8
 
9
9
  The workspace root may also have a `package.json`. That file is workspace metadata (name/version). It is **not** a project unless it also matches `includeProjects`.
10
10
 
11
+ **Standalone projects.** A folder with a `package.json` and no `projects/` folder, and no `includeProjects` of your own, is one project: its root `package.json` is the package, with its sources at the root (build output, `node_modules` and `.git` excluded). Every tool works on that one package; `forge dependencies sync`, `dependencies report` and `paths` have nothing to do, since there is no other package. Adding a `projects/` folder turns it into a monorepo: move the sources into `projects/<name>/` with their own `package.json`.
12
+
11
13
  ### Defaults
12
14
 
13
15
  | Option | Default |
@@ -42,7 +44,7 @@ export default {
42
44
 
43
45
  Per-project settings go under `projects`, keyed by package name: `docker` (image, Dockerfile, tags, build args) and `ui` (the UI project a deployable ships, see [forge](../forge/README.md#shipping-the-ui-with-the-api)).
44
46
 
45
- `--config-file` on `forge` / `flux` selects a different config path. The default filename is still `.decohere.js`.
47
+ `--config-file` on `forge` / `flux` selects a different config path. The default is `.decohere.js` in the workspace root (`--root-dir`, else the current folder).
46
48
 
47
49
  ### Package aliases
48
50
 
@@ -12,6 +12,7 @@ const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
12
12
  const _io = require("@decohere/core/io");
13
13
  const _io1 = require("@decohere/devkit/io");
14
14
  const _objects = require("@decohere/core/objects");
15
+ const _const = require("../const.js");
15
16
  const _errors = require("../errors.js");
16
17
  const _packagejsonmodel = require("../models/package-json.model.js");
17
18
  const _projectmodel = require("../models/project.model.js");
@@ -29,9 +30,39 @@ class ProjectFactory {
29
30
  ignore: ignoreProjects
30
31
  });
31
32
  const packageJsonFiles = ProjectFactory.filterNestedPackageJsonFiles(discoveredPackageJsonFiles);
33
+ if (packageJsonFiles.length === 0 && await ProjectFactory.isStandalone(config)) {
34
+ return [
35
+ await ProjectFactory.standaloneProject(config)
36
+ ];
37
+ }
32
38
  const projects = await Promise.all(packageJsonFiles.map((file)=>ProjectFactory.projectFrom(file, config)));
33
39
  return projects.sort((a, b)=>a.name.localeCompare(b.name));
34
40
  }
41
+ /**
42
+ * A standalone project: no `projects/` folder and no `includeProjects` of the user's own, but
43
+ * a root `package.json`. That `package.json` is then the one project, at the root.
44
+ */ static async isStandalone(config) {
45
+ const rootDir = config.rootDir ?? "./";
46
+ const defaultInclude = JSON.stringify(config.includeProjects) === JSON.stringify(_const.WorkspaceDefaults.includeProjects);
47
+ return defaultInclude && !await _io.io.directoryExists(_nodepath.default.join(rootDir, _const.PROJECTS_DIR)) && await _io.io.fileExists(_nodepath.default.join(rootDir, _const.PACKAGE_JSON));
48
+ }
49
+ /** The root `package.json` as the one project. Build output and `.git` aren't its sources. */ static async standaloneProject(config) {
50
+ const [packageJsonFile] = await (0, _io1.findFiles)([
51
+ _const.PACKAGE_JSON
52
+ ], {
53
+ cwd: config.rootDir ?? "./"
54
+ });
55
+ const outDir = (config.outDir ?? _const.WorkspaceDefaults.outDir).replace(/[/\\]+$/, "");
56
+ const ignore = [
57
+ ...config.ignore ?? [],
58
+ `${outDir}/**`,
59
+ ".git/**"
60
+ ];
61
+ return ProjectFactory.projectFrom(packageJsonFile, {
62
+ ...config,
63
+ ignore
64
+ });
65
+ }
35
66
  static filterNestedPackageJsonFiles(files) {
36
67
  const normalize = (filePath)=>filePath.replaceAll("\\", "/");
37
68
  const packageJsonSet = new Set(files.map((file)=>normalize(file.relativePath)));
@@ -48,9 +48,6 @@ _export(exports, {
48
48
  get WorkspaceFactory () {
49
49
  return _workspacefactory.WorkspaceFactory;
50
50
  },
51
- get findWorkspaceRoot () {
52
- return _findworkspaceroot.findWorkspaceRoot;
53
- },
54
51
  get packageAliases () {
55
52
  return _packagealiases.packageAliases;
56
53
  },
@@ -61,7 +58,6 @@ _export(exports, {
61
58
  const _const = require("./const.js");
62
59
  const _errors = require("./errors.js");
63
60
  const _configfactory = require("./factories/config.factory.js");
64
- const _findworkspaceroot = require("./factories/find-workspace-root.js");
65
61
  const _projectfactory = require("./factories/project.factory.js");
66
62
  const _workspacefactory = require("./factories/workspace.factory.js");
67
63
  const _haspackagejsonmixin = require("./models/has-package-json.mixin.js");
@@ -68,6 +68,13 @@ class Workspace extends (0, _extensibility.mixwith)(_haspackagejsonmixin.HasPack
68
68
  get rootDir() {
69
69
  return this.#config.rootDir ?? "./";
70
70
  }
71
+ /**
72
+ * One project, whose `package.json` is the root's: a folder without `projects/`. Commands
73
+ * that write a project's manifest from the root's (dependency sync) or map projects to each
74
+ * other (paths) have nothing to do then.
75
+ */ get isStandalone() {
76
+ return this.#projectSeeds.length === 1 && this.#projectSeeds[0]?.config?.projectDir === ".";
77
+ }
71
78
  get packageJsonDirectory() {
72
79
  return this.rootDir;
73
80
  }
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { io } from "@decohere/core/io";
3
3
  import { findFiles } from "@decohere/devkit/io";
4
4
  import { merge } from "@decohere/core/objects";
5
+ import { PACKAGE_JSON, PROJECTS_DIR, WorkspaceDefaults } from "../const.js";
5
6
  import { WorkspaceConfigurationError } from "../errors.js";
6
7
  import { PackageJson } from "../models/package-json.model.js";
7
8
  import { Project } from "../models/project.model.js";
@@ -14,9 +15,39 @@ export class ProjectFactory {
14
15
  ignore: ignoreProjects
15
16
  });
16
17
  const packageJsonFiles = ProjectFactory.filterNestedPackageJsonFiles(discoveredPackageJsonFiles);
18
+ if (packageJsonFiles.length === 0 && await ProjectFactory.isStandalone(config)) {
19
+ return [
20
+ await ProjectFactory.standaloneProject(config)
21
+ ];
22
+ }
17
23
  const projects = await Promise.all(packageJsonFiles.map((file)=>ProjectFactory.projectFrom(file, config)));
18
24
  return projects.sort((a, b)=>a.name.localeCompare(b.name));
19
25
  }
26
+ /**
27
+ * A standalone project: no `projects/` folder and no `includeProjects` of the user's own, but
28
+ * a root `package.json`. That `package.json` is then the one project, at the root.
29
+ */ static async isStandalone(config) {
30
+ const rootDir = config.rootDir ?? "./";
31
+ const defaultInclude = JSON.stringify(config.includeProjects) === JSON.stringify(WorkspaceDefaults.includeProjects);
32
+ return defaultInclude && !await io.directoryExists(path.join(rootDir, PROJECTS_DIR)) && await io.fileExists(path.join(rootDir, PACKAGE_JSON));
33
+ }
34
+ /** The root `package.json` as the one project. Build output and `.git` aren't its sources. */ static async standaloneProject(config) {
35
+ const [packageJsonFile] = await findFiles([
36
+ PACKAGE_JSON
37
+ ], {
38
+ cwd: config.rootDir ?? "./"
39
+ });
40
+ const outDir = (config.outDir ?? WorkspaceDefaults.outDir).replace(/[/\\]+$/, "");
41
+ const ignore = [
42
+ ...config.ignore ?? [],
43
+ `${outDir}/**`,
44
+ ".git/**"
45
+ ];
46
+ return ProjectFactory.projectFrom(packageJsonFile, {
47
+ ...config,
48
+ ignore
49
+ });
50
+ }
20
51
  static filterNestedPackageJsonFiles(files) {
21
52
  const normalize = (filePath)=>filePath.replaceAll("\\", "/");
22
53
  const packageJsonSet = new Set(files.map((file)=>normalize(file.relativePath)));
package/es6/src/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  export { WORKSPACE_CONFIG, WorkspaceDefaults } from "./const.js";
2
2
  export { WorkspaceConfigurationError } from "./errors.js";
3
3
  export { UserConfigFactory } from "./factories/config.factory.js";
4
- export { findWorkspaceRoot } from "./factories/find-workspace-root.js";
5
4
  export { ProjectFactory } from "./factories/project.factory.js";
6
5
  export { WorkspaceFactory } from "./factories/workspace.factory.js";
7
6
  export { HasPackageJson } from "./models/has-package-json.mixin.js";
@@ -58,6 +58,13 @@ export class Workspace extends mixwith(HasPackageJson, HasTsConfig) {
58
58
  get rootDir() {
59
59
  return this.#config.rootDir ?? "./";
60
60
  }
61
+ /**
62
+ * One project, whose `package.json` is the root's: a folder without `projects/`. Commands
63
+ * that write a project's manifest from the root's (dependency sync) or map projects to each
64
+ * other (paths) have nothing to do then.
65
+ */ get isStandalone() {
66
+ return this.#projectSeeds.length === 1 && this.#projectSeeds[0]?.config?.projectDir === ".";
67
+ }
61
68
  get packageJsonDirectory() {
62
69
  return this.rootDir;
63
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decohere/workspace",
3
- "version": "0.0.1-dev.128",
3
+ "version": "0.0.1-dev.130",
4
4
  "description": "Workspace and project model primitives for monorepo tooling",
5
5
  "keywords": [
6
6
  "typescript",
@@ -33,9 +33,9 @@
33
33
  "test": "echo \"Error: no test specified\" && exit 1"
34
34
  },
35
35
  "dependencies": {
36
- "@decohere/core": "0.0.1-dev.128",
37
- "@decohere/crucible": "0.0.1-dev.128",
38
- "@decohere/devkit": "0.0.1-dev.128",
36
+ "@decohere/core": "0.0.1-dev.130",
37
+ "@decohere/crucible": "0.0.1-dev.130",
38
+ "@decohere/devkit": "0.0.1-dev.130",
39
39
  "picomatch": "^4.0.4",
40
40
  "rxjs": "^7.8.2",
41
41
  "typescript": "^6.0.3"
@@ -2,6 +2,13 @@ import { Project, type ProjectSeed } from "../models/project.model.js";
2
2
  import type { WorkspaceOptions } from "../models/workspace-config.type.js";
3
3
  export declare class ProjectFactory {
4
4
  static getProjectSeeds(config: WorkspaceOptions): Promise<ProjectSeed[]>;
5
+ /**
6
+ * A standalone project: no `projects/` folder and no `includeProjects` of the user's own, but
7
+ * a root `package.json`. That `package.json` is then the one project, at the root.
8
+ */
9
+ private static isStandalone;
10
+ /** The root `package.json` as the one project. Build output and `.git` aren't its sources. */
11
+ private static standaloneProject;
5
12
  private static filterNestedPackageJsonFiles;
6
13
  static getProjects(config: WorkspaceOptions, configuration?: string): Promise<Project[]>;
7
14
  private static projectFrom;
@@ -1,7 +1,6 @@
1
1
  export { WORKSPACE_CONFIG, WorkspaceDefaults } from "./const.js";
2
2
  export { WorkspaceConfigurationError } from "./errors.js";
3
3
  export { UserConfigFactory } from "./factories/config.factory.js";
4
- export { findWorkspaceRoot } from "./factories/find-workspace-root.js";
5
4
  export { ProjectFactory } from "./factories/project.factory.js";
6
5
  export { WorkspaceFactory } from "./factories/workspace.factory.js";
7
6
  export { HasPackageJson } from "./models/has-package-json.mixin.js";
@@ -19,6 +19,12 @@ export declare class Workspace extends Workspace_base {
19
19
  protected get projectSeeds(): readonly ProjectSeed[];
20
20
  getProjects(configuration: string): readonly Project[];
21
21
  get rootDir(): string;
22
+ /**
23
+ * One project, whose `package.json` is the root's: a folder without `projects/`. Commands
24
+ * that write a project's manifest from the root's (dependency sync) or map projects to each
25
+ * other (paths) have nothing to do then.
26
+ */
27
+ get isStandalone(): boolean;
22
28
  get packageJsonDirectory(): string;
23
29
  protected get tsConfigDirectory(): string;
24
30
  protected get fallbackTsConfigDirectory(): string | undefined;
@@ -1,37 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "findWorkspaceRoot", {
6
- enumerable: true,
7
- get: function() {
8
- return findWorkspaceRoot;
9
- }
10
- });
11
- const _nodefs = require("node:fs");
12
- const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
13
- const _const = require("../const.js");
14
- function _interop_require_default(obj) {
15
- return obj && obj.__esModule ? obj : {
16
- default: obj
17
- };
18
- }
19
- function findWorkspaceRoot(startDir) {
20
- const start = _nodepath.default.resolve(startDir);
21
- let dir = start;
22
- for(;;){
23
- if ((0, _nodefs.existsSync)(_nodepath.default.join(dir, _const.PACKAGE_JSON)) && isDirectory(_nodepath.default.join(dir, _const.PROJECTS_DIR))) {
24
- return dir;
25
- }
26
- const parent = _nodepath.default.dirname(dir);
27
- if (parent === dir) return start;
28
- dir = parent;
29
- }
30
- }
31
- function isDirectory(fullPath) {
32
- try {
33
- return (0, _nodefs.statSync)(fullPath).isDirectory();
34
- } catch {
35
- return false;
36
- }
37
- }
@@ -1,26 +0,0 @@
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
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * The workspace root for a command run from `startDir`: the nearest directory at or above
3
- * it with a `package.json` and a `projects/` folder. Falls back to `startDir`, so running
4
- * from a package directory (`projects/api`) still sees the whole workspace.
5
- */
6
- export declare function findWorkspaceRoot(startDir: string): string;