outfitter 0.2.6 → 0.2.7

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 (147) hide show
  1. package/README.md +52 -26
  2. package/dist/actions.d.ts +1 -1
  3. package/dist/actions.js +20 -19
  4. package/dist/cli.js +3 -2
  5. package/dist/commands/add.d.ts +4 -4
  6. package/dist/commands/check-tsdoc.d.ts +22 -0
  7. package/dist/commands/check-tsdoc.js +8 -0
  8. package/dist/commands/check.d.ts +12 -12
  9. package/dist/commands/check.js +1 -1
  10. package/dist/commands/demo.d.ts +3 -3
  11. package/dist/commands/docs-module-loader.d.ts +1 -1
  12. package/dist/commands/doctor.d.ts +2 -2
  13. package/dist/commands/doctor.js +13 -1
  14. package/dist/commands/init.d.ts +6 -6
  15. package/dist/commands/init.js +12 -11
  16. package/dist/commands/repo.d.ts +2 -2
  17. package/dist/commands/scaffold.d.ts +3 -3
  18. package/dist/commands/scaffold.js +12 -11
  19. package/dist/commands/shared-deps.d.ts +1 -16
  20. package/dist/commands/shared-deps.js +2 -1
  21. package/dist/commands/upgrade-codemods.d.ts +4 -4
  22. package/dist/commands/upgrade-codemods.js +2 -2
  23. package/dist/commands/upgrade-planner.d.ts +6 -6
  24. package/dist/commands/upgrade-workspace.d.ts +1 -75
  25. package/dist/commands/upgrade.d.ts +66 -59
  26. package/dist/commands/upgrade.js +2 -2
  27. package/dist/create/index.d.ts +4 -4
  28. package/dist/create/index.js +13 -12
  29. package/dist/create/planner.d.ts +2 -2
  30. package/dist/create/planner.js +9 -8
  31. package/dist/create/presets.d.ts +2 -2
  32. package/dist/create/types.d.ts +1 -1
  33. package/dist/engine/blocks.d.ts +2 -2
  34. package/dist/engine/blocks.js +1 -1
  35. package/dist/engine/collector.d.ts +1 -1
  36. package/dist/engine/config.d.ts +2 -2
  37. package/dist/engine/config.js +5 -2
  38. package/dist/engine/dependency-versions.d.ts +12 -0
  39. package/dist/engine/dependency-versions.js +12 -0
  40. package/dist/engine/executor.d.ts +2 -2
  41. package/dist/engine/executor.js +7 -4
  42. package/dist/engine/index.d.ts +8 -8
  43. package/dist/engine/index.js +28 -19
  44. package/dist/engine/names.d.ts +2 -2
  45. package/dist/engine/names.js +10 -2
  46. package/dist/engine/post-scaffold.d.ts +2 -2
  47. package/dist/engine/render-plan.d.ts +1 -1
  48. package/dist/engine/template.d.ts +2 -2
  49. package/dist/engine/types.d.ts +1 -1
  50. package/dist/engine/workspace.d.ts +3 -3
  51. package/dist/engine/workspace.js +8 -1
  52. package/dist/index.d.ts +93 -132
  53. package/dist/index.js +1 -9
  54. package/dist/manifest.d.ts +4 -4
  55. package/dist/output-mode.d.ts +1 -1
  56. package/dist/shared/{chunk-k59f60cp.js → chunk-x6644tk8.js} +3017 -2520
  57. package/dist/shared/outfitter-109s75x0.d.ts +76 -0
  58. package/dist/shared/{outfitter-2np85etz.js → outfitter-1fy7byz5.js} +76 -1
  59. package/dist/shared/outfitter-20f6a2n4.js +35 -0
  60. package/dist/shared/outfitter-4q1zfmvc.js +154 -0
  61. package/dist/shared/{outfitter-66b25bj8.js → outfitter-5akzvppx.js} +4 -4
  62. package/dist/shared/outfitter-5y646xzk.js +301 -0
  63. package/dist/shared/{outfitter-j8yc7294.d.ts → outfitter-5yjr404v.d.ts} +4 -4
  64. package/dist/shared/outfitter-63gse8fv.js +316 -0
  65. package/dist/shared/{outfitter-rdc5v5ms.js → outfitter-7ch26yq8.js} +210 -71
  66. package/dist/shared/{outfitter-ksa1pp4t.d.ts → outfitter-dpj9erew.d.ts} +1 -1
  67. package/dist/shared/{outfitter-sgtq57qr.d.ts → outfitter-f9znfhkn.d.ts} +1 -1
  68. package/dist/shared/outfitter-fhnjpjwc.d.ts +18 -0
  69. package/dist/shared/{outfitter-193jvzg4.d.ts → outfitter-fn20r49x.d.ts} +1 -1
  70. package/dist/shared/{outfitter-cwq39bv4.d.ts → outfitter-h3q6ae6d.d.ts} +17 -17
  71. package/dist/shared/outfitter-ha89qf8q.js +132 -0
  72. package/dist/shared/{outfitter-pyy1zkfh.d.ts → outfitter-hsp8vy5m.d.ts} +25 -12
  73. package/dist/shared/outfitter-m3ehh37q.d.ts +22 -0
  74. package/dist/shared/outfitter-m44n0qzw.js +161 -0
  75. package/dist/shared/{outfitter-qn864k6h.js → outfitter-mt7d1ek2.js} +152 -35
  76. package/dist/shared/{outfitter-avhm5z6w.js → outfitter-p71qb0f0.js} +4 -4
  77. package/dist/shared/{outfitter-9zqc2njf.js → outfitter-pcj9gg2g.js} +90 -40
  78. package/dist/shared/{outfitter-hws10ze7.js → outfitter-pj9vp00r.js} +87 -18
  79. package/dist/shared/{outfitter-gdvm5c0b.d.ts → outfitter-qakwgrrh.d.ts} +1 -1
  80. package/dist/shared/{outfitter-k56rmt24.d.ts → outfitter-r419zfgs.d.ts} +6 -6
  81. package/dist/shared/{outfitter-33w361tc.d.ts → outfitter-s7jetkge.d.ts} +1 -1
  82. package/dist/shared/{outfitter-1dvma85c.js → outfitter-w1j80j1r.js} +4 -0
  83. package/dist/shared/{outfitter-1dd0k853.js → outfitter-xe5mzgdc.js} +21 -7
  84. package/dist/shared/{outfitter-h1mnzzd1.d.ts → outfitter-ybbazsxq.d.ts} +1 -1
  85. package/dist/shared/{outfitter-2ngep1h2.d.ts → outfitter-yraebrmw.d.ts} +1 -1
  86. package/dist/shared/{outfitter-bkwpbkr9.d.ts → outfitter-z0we32cp.d.ts} +21 -21
  87. package/dist/shared/{outfitter-3weh61w7.d.ts → outfitter-z5sx06qe.d.ts} +12 -12
  88. package/dist/targets/index.d.ts +3 -3
  89. package/dist/targets/index.js +1 -1
  90. package/dist/targets/registry.d.ts +2 -2
  91. package/dist/targets/registry.js +1 -1
  92. package/dist/targets/types.d.ts +1 -1
  93. package/package.json +29 -19
  94. package/template-versions.json +22 -0
  95. package/templates/basic/package.json.template +6 -6
  96. package/templates/cli/biome.json.template +1 -1
  97. package/templates/cli/package.json.template +17 -9
  98. package/templates/daemon/biome.json.template +1 -1
  99. package/templates/daemon/package.json.template +18 -10
  100. package/templates/full-stack/.gitignore.template +30 -0
  101. package/templates/full-stack/README.md.template +30 -0
  102. package/templates/full-stack/apps/cli/package.json.template +39 -0
  103. package/templates/full-stack/apps/cli/src/cli.ts.template +24 -0
  104. package/templates/full-stack/apps/cli/src/index.test.ts.template +18 -0
  105. package/templates/full-stack/apps/cli/src/index.ts.template +5 -0
  106. package/templates/full-stack/apps/cli/tsconfig.json.template +37 -0
  107. package/templates/full-stack/apps/mcp/package.json.template +40 -0
  108. package/templates/full-stack/apps/mcp/src/index.test.ts.template +18 -0
  109. package/templates/full-stack/apps/mcp/src/index.ts.template +6 -0
  110. package/templates/full-stack/apps/mcp/src/mcp.ts.template +22 -0
  111. package/templates/full-stack/apps/mcp/src/server.ts.template +10 -0
  112. package/templates/full-stack/apps/mcp/tsconfig.json.template +37 -0
  113. package/templates/full-stack/package.json.template +16 -0
  114. package/templates/full-stack/packages/core/package.json.template +36 -0
  115. package/templates/full-stack/packages/core/src/handlers.ts.template +31 -0
  116. package/templates/full-stack/packages/core/src/index.test.ts.template +30 -0
  117. package/templates/full-stack/packages/core/src/index.ts.template +8 -0
  118. package/templates/full-stack/packages/core/src/types.ts.template +13 -0
  119. package/templates/full-stack/packages/core/tsconfig.json.template +34 -0
  120. package/templates/library/.gitignore.template +30 -0
  121. package/templates/library/README.md.template +29 -0
  122. package/templates/library/bunup.config.ts.template +20 -0
  123. package/templates/library/package.json.template +55 -0
  124. package/templates/library/src/handlers.ts.template +31 -0
  125. package/templates/library/src/index.test.ts.template +35 -0
  126. package/templates/library/src/index.ts.template +8 -0
  127. package/templates/library/src/types.ts.template +13 -0
  128. package/templates/library/tsconfig.json.template +34 -0
  129. package/templates/mcp/biome.json.template +1 -1
  130. package/templates/mcp/package.json.template +17 -9
  131. package/templates/mcp/src/index.ts.template +1 -1
  132. package/templates/mcp/src/mcp.ts.template +28 -74
  133. package/templates/mcp/src/server.ts.template +2 -9
  134. package/templates/minimal/package.json.template +13 -6
  135. package/dist/commands/migrate-kit.d.ts +0 -2
  136. package/dist/commands/migrate-kit.js +0 -15
  137. package/dist/shared/outfitter-7ha7p61k.d.ts +0 -6
  138. package/dist/shared/outfitter-ara3djt0.js +0 -73
  139. package/dist/shared/outfitter-d7pq7d0k.js +0 -196
  140. package/dist/shared/outfitter-k112c427.js +0 -21
  141. package/dist/shared/outfitter-mxz69pgy.js +0 -713
  142. package/dist/shared/outfitter-npemy7ta.d.ts +0 -53
  143. package/dist/shared/outfitter-npyfbdmc.d.ts +0 -6
  144. package/dist/shared/outfitter-q9agarmb.js +0 -42
  145. package/dist/shared/{outfitter-dd0btgec.d.ts → outfitter-6fgk6adm.d.ts} +5 -5
  146. package/dist/shared/{outfitter-e2zz5wv7.d.ts → outfitter-e9rrfekb.d.ts} +8 -8
  147. package/dist/shared/{outfitter-qfh36ddg.d.ts → outfitter-n9g1zk4x.d.ts} +7 -7
@@ -0,0 +1,76 @@
1
+ import { OutfitterError } from "@outfitter/contracts";
2
+ import { Result } from "@outfitter/contracts";
3
+ /** A package dependency found across workspace manifests. */
4
+ interface WorkspacePackageEntry {
5
+ /** Full package name (e.g. "@outfitter/cli") */
6
+ readonly name: string;
7
+ /** Cleaned semver version (without range prefix) */
8
+ readonly version: string;
9
+ }
10
+ /** Version conflict: same package at different versions in different manifests. */
11
+ interface VersionConflict {
12
+ /** Full package name */
13
+ readonly name: string;
14
+ /** All distinct versions found, with their manifest paths */
15
+ readonly versions: ReadonlyArray<{
16
+ readonly version: string;
17
+ readonly manifests: readonly string[];
18
+ }>;
19
+ }
20
+ /** Result of scanning a workspace for @outfitter/* packages. */
21
+ interface WorkspaceScanResult {
22
+ /** Version conflicts found across manifests */
23
+ readonly conflicts: readonly VersionConflict[];
24
+ /** All manifest paths scanned */
25
+ readonly manifestPaths: readonly string[];
26
+ /** Maps package name to the manifest paths that contain it */
27
+ readonly manifestsByPackage: ReadonlyMap<string, readonly string[]>;
28
+ /** Deduplicated @outfitter/* packages (uses lowest version for conflicts) */
29
+ readonly packages: readonly WorkspacePackageEntry[];
30
+ /** Workspace root directory (null if not a workspace) */
31
+ readonly workspaceRoot: string | null;
32
+ }
33
+ /**
34
+ * Walk up from `cwd` looking for a workspace root.
35
+ *
36
+ * Workspace root is identified by:
37
+ * - `package.json` with a `workspaces` field (npm/yarn/bun)
38
+ * - `pnpm-workspace.yaml` file
39
+ *
40
+ * Returns `null` (as Ok) if no workspace root found — this is not an error,
41
+ * it means the project is a standalone package.
42
+ */
43
+ declare function detectWorkspaceRoot(cwd: string): Result<string | null, OutfitterError>;
44
+ /**
45
+ * Collect all package.json files from a workspace root directory.
46
+ *
47
+ * Reads workspace patterns from the root package.json, resolves globs,
48
+ * and returns sorted absolute paths to all matching package.json files.
49
+ * Always includes the root package.json itself.
50
+ */
51
+ declare function collectWorkspaceManifests(rootDir: string): Result<string[], OutfitterError>;
52
+ /**
53
+ * Scan all workspace manifests, collect @outfitter/* deps,
54
+ * deduplicate, and detect version conflicts.
55
+ *
56
+ * For deduplication: when the same package appears at the same version
57
+ * in multiple manifests, it appears once in the result.
58
+ * When versions differ, the lowest version is used and a conflict is reported.
59
+ */
60
+ declare function getInstalledPackagesFromWorkspace(rootDir: string): Result<WorkspaceScanResult, OutfitterError>;
61
+ /**
62
+ * Apply version updates to all manifests in a workspace that contain
63
+ * the specified @outfitter/* packages.
64
+ *
65
+ * Preserves the existing version range prefix (^, ~, >=, etc.) in each manifest.
66
+ * Does NOT run `bun install` — the caller is responsible for that.
67
+ */
68
+ declare function applyUpdatesToWorkspace(manifestPaths: readonly string[], manifestsByPackage: ReadonlyMap<string, readonly string[]>, updates: readonly {
69
+ name: string;
70
+ latestVersion: string;
71
+ }[]): Promise<Result<void, OutfitterError>>;
72
+ /**
73
+ * Run `bun install` at the given directory.
74
+ */
75
+ declare function runInstall(cwd: string): Promise<Result<void, OutfitterError>>;
76
+ export { WorkspacePackageEntry, VersionConflict, WorkspaceScanResult, detectWorkspaceRoot, collectWorkspaceManifests, getInstalledPackagesFromWorkspace, applyUpdatesToWorkspace, runInstall };
@@ -1,4 +1,7 @@
1
1
  // @bun
2
+ import {
3
+ sanitizePackageName
4
+ } from "./outfitter-4q1zfmvc.js";
2
5
  import {
3
6
  ScaffoldError
4
7
  } from "./outfitter-8y2dfx6n.js";
@@ -7,6 +10,62 @@ import {
7
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
8
11
  import { dirname, join, resolve } from "path";
9
12
  import { Result } from "@outfitter/contracts";
13
+ function deriveWorkspaceScopeForExamples(workspaceName) {
14
+ const sanitized = sanitizePackageName(workspaceName);
15
+ if (sanitized.startsWith("@")) {
16
+ const separator = sanitized.indexOf("/");
17
+ if (separator > 1) {
18
+ return sanitized.slice(0, separator);
19
+ }
20
+ if (sanitized.length > 1) {
21
+ return sanitized;
22
+ }
23
+ }
24
+ if (sanitized.length > 0) {
25
+ return `@${sanitized}`;
26
+ }
27
+ return "@your-scope";
28
+ }
29
+ function buildWorkspaceRootReadme(workspaceName) {
30
+ const workspaceScope = deriveWorkspaceScopeForExamples(workspaceName);
31
+ return `# ${workspaceName}
32
+
33
+ ## Structure
34
+
35
+ \`\`\`
36
+ ${workspaceName}/
37
+ \u251C\u2500\u2500 apps/ # Runnable applications (CLI, MCP, daemon, API)
38
+ \u251C\u2500\u2500 packages/ # Shared libraries
39
+ \u2514\u2500\u2500 package.json # Workspace root
40
+ \`\`\`
41
+
42
+ ## Getting Started
43
+
44
+ \`\`\`bash
45
+ # Install all dependencies
46
+ bun install
47
+
48
+ # Build all packages
49
+ bun run build
50
+
51
+ # Run tests
52
+ bun run test
53
+
54
+ # Typecheck
55
+ bun run typecheck
56
+ \`\`\`
57
+
58
+ ## Adding Packages
59
+
60
+ \`\`\`bash
61
+ # Add a new app
62
+ outfitter init --name ${workspaceScope}/my-app --preset cli
63
+
64
+ # Add a shared library
65
+ outfitter init --name ${workspaceScope}/my-lib --preset library
66
+ \`\`\`
67
+ `;
68
+ }
10
69
  function buildWorkspaceRootPackageJson(workspaceName) {
11
70
  const workspacePackage = {
12
71
  name: workspaceName,
@@ -38,6 +97,10 @@ function scaffoldWorkspaceRoot(rootDir, workspaceName, force) {
38
97
  mkdirSync(join(rootDir, "apps"), { recursive: true });
39
98
  mkdirSync(join(rootDir, "packages"), { recursive: true });
40
99
  writeFileSync(packageJsonPath, buildWorkspaceRootPackageJson(workspaceName), "utf-8");
100
+ const readmePath = join(rootDir, "README.md");
101
+ if (force || !existsSync(readmePath)) {
102
+ writeFileSync(readmePath, buildWorkspaceRootReadme(workspaceName), "utf-8");
103
+ }
41
104
  const gitignorePath = join(rootDir, ".gitignore");
42
105
  if (force || !existsSync(gitignorePath)) {
43
106
  writeFileSync(gitignorePath, `node_modules
@@ -50,6 +113,18 @@ function scaffoldWorkspaceRoot(rootDir, workspaceName, force) {
50
113
  return Result.err(new ScaffoldError(`Failed to scaffold workspace root: ${message}`));
51
114
  }
52
115
  }
116
+ function getWorkspacePatterns(workspaces) {
117
+ if (Array.isArray(workspaces)) {
118
+ return workspaces.filter((entry) => typeof entry === "string");
119
+ }
120
+ if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
121
+ const packages = workspaces.packages;
122
+ if (Array.isArray(packages)) {
123
+ return packages.filter((entry) => typeof entry === "string");
124
+ }
125
+ }
126
+ return [];
127
+ }
53
128
  function hasWorkspacesField(pkg) {
54
129
  const workspaces = pkg.workspaces;
55
130
  if (Array.isArray(workspaces) && workspaces.length > 0) {
@@ -92,4 +167,4 @@ function detectWorkspaceRoot(cwd) {
92
167
  return Result.ok(null);
93
168
  }
94
169
 
95
- export { buildWorkspaceRootPackageJson, scaffoldWorkspaceRoot, detectWorkspaceRoot };
170
+ export { buildWorkspaceRootReadme, buildWorkspaceRootPackageJson, scaffoldWorkspaceRoot, getWorkspacePatterns, hasWorkspacesField, detectWorkspaceRoot };
@@ -0,0 +1,35 @@
1
+ // @bun
2
+ import {
3
+ resolveTemplateDependencyVersions
4
+ } from "./outfitter-m44n0qzw.js";
5
+
6
+ // apps/outfitter/src/commands/shared-deps.ts
7
+ var _dependencyVersions;
8
+ function getDependencyVersions() {
9
+ if (!_dependencyVersions) {
10
+ _dependencyVersions = resolveTemplateDependencyVersions();
11
+ }
12
+ return _dependencyVersions;
13
+ }
14
+ function pickVersion(source, name, fallback) {
15
+ return source[name] ?? fallback;
16
+ }
17
+ var SHARED_DEV_DEPS = {
18
+ "@biomejs/biome": pickVersion(getDependencyVersions().external, "@biomejs/biome", "^2.4.4"),
19
+ "@outfitter/tooling": pickVersion(getDependencyVersions().internal, "@outfitter/tooling", "^0.2.4"),
20
+ "@types/bun": pickVersion(getDependencyVersions().external, "@types/bun", "^1.3.9"),
21
+ lefthook: pickVersion(getDependencyVersions().external, "lefthook", "^2.1.1"),
22
+ typescript: pickVersion(getDependencyVersions().external, "typescript", "^5.9.3"),
23
+ ultracite: pickVersion(getDependencyVersions().external, "ultracite", "^7.2.3")
24
+ };
25
+ var SHARED_SCRIPTS = {
26
+ check: "ultracite check",
27
+ "clean:artifacts": "rm -rf dist .turbo",
28
+ "verify:ci": "bun run typecheck && bun run check && bun run build && bun run test",
29
+ lint: "biome check .",
30
+ "lint:fix": "biome check . --write",
31
+ format: "biome format --write .",
32
+ typecheck: "tsc --noEmit"
33
+ };
34
+
35
+ export { SHARED_DEV_DEPS, SHARED_SCRIPTS };
@@ -0,0 +1,154 @@
1
+ // @bun
2
+ // apps/outfitter/src/engine/names.ts
3
+ import { basename, isAbsolute, relative } from "path";
4
+ function deriveProjectName(packageName) {
5
+ const trimmed = packageName.trim();
6
+ if (!trimmed.startsWith("@")) {
7
+ return trimmed;
8
+ }
9
+ const scopeSeparator = trimmed.indexOf("/");
10
+ if (scopeSeparator < 0) {
11
+ return trimmed;
12
+ }
13
+ return trimmed.slice(scopeSeparator + 1).trim();
14
+ }
15
+ function deriveBinName(projectName) {
16
+ return projectName.toLowerCase().replace(/\s+/g, "-");
17
+ }
18
+ function resolveAuthor() {
19
+ const fromEnv = process.env["GIT_AUTHOR_NAME"] ?? process.env["GIT_COMMITTER_NAME"] ?? process.env["AUTHOR"] ?? process.env["USER"] ?? process.env["USERNAME"];
20
+ if (fromEnv) {
21
+ return fromEnv;
22
+ }
23
+ try {
24
+ const result = Bun.spawnSync(["git", "config", "--get", "user.name"], {
25
+ stdout: "pipe",
26
+ stderr: "ignore"
27
+ });
28
+ if (result.exitCode === 0) {
29
+ const value = result.stdout.toString().trim();
30
+ return value.length > 0 ? value : "";
31
+ }
32
+ } catch {}
33
+ return "";
34
+ }
35
+ function resolveYear() {
36
+ return String(new Date().getFullYear());
37
+ }
38
+ function resolvePackageName(targetDir, name) {
39
+ return name ?? basename(targetDir);
40
+ }
41
+ var WINDOWS_ABSOLUTE_PATH_RE = /^[a-zA-Z]:[\\/]/;
42
+ var PACKAGE_SEGMENT_RE = /^[a-z0-9._-]+$/;
43
+ var RESERVED_PACKAGE_NAMES = new Set(["node_modules", "favicon.ico"]);
44
+ function validateProjectDirectoryName(name) {
45
+ const trimmed = name.trim();
46
+ if (trimmed.length === 0) {
47
+ return "must not be empty";
48
+ }
49
+ if (trimmed === "." || trimmed === "..") {
50
+ return "must not be '.' or '..'";
51
+ }
52
+ if (isAbsolute(trimmed) || WINDOWS_ABSOLUTE_PATH_RE.test(trimmed)) {
53
+ return "must not be an absolute path";
54
+ }
55
+ if (trimmed.includes("/") || trimmed.includes("\\")) {
56
+ return "must not contain path separators";
57
+ }
58
+ return;
59
+ }
60
+ function isPathWithin(basePath, targetPath) {
61
+ const relativePath = relative(basePath, targetPath);
62
+ if (relativePath === "") {
63
+ return true;
64
+ }
65
+ if (isAbsolute(relativePath)) {
66
+ return false;
67
+ }
68
+ const segments = relativePath.split(/[\\/]/);
69
+ return !segments.includes("..");
70
+ }
71
+ function sanitizePackageSegment(value) {
72
+ return value.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[._-]+|[._-]+$/g, "");
73
+ }
74
+ function sanitizePackageName(name) {
75
+ const trimmed = name.trim();
76
+ if (trimmed.startsWith("@")) {
77
+ const separator = trimmed.indexOf("/");
78
+ if (separator > 1 && separator < trimmed.length - 1) {
79
+ const scope = sanitizePackageSegment(trimmed.slice(1, separator));
80
+ const pkg = sanitizePackageSegment(trimmed.slice(separator + 1));
81
+ if (scope.length > 0 && pkg.length > 0) {
82
+ return `@${scope}/${pkg}`;
83
+ }
84
+ if (pkg.length > 0) {
85
+ return pkg;
86
+ }
87
+ if (scope.length > 0) {
88
+ return scope;
89
+ }
90
+ return "";
91
+ }
92
+ }
93
+ return sanitizePackageSegment(trimmed);
94
+ }
95
+ function validatePackageSegment(segment, kind) {
96
+ if (segment.length === 0) {
97
+ return `${kind} must not be empty`;
98
+ }
99
+ if (!PACKAGE_SEGMENT_RE.test(segment)) {
100
+ return `${kind} contains invalid characters`;
101
+ }
102
+ if (kind === "name" && (segment.startsWith(".") || segment.startsWith("_"))) {
103
+ return `${kind} must not start with '.' or '_'`;
104
+ }
105
+ return;
106
+ }
107
+ function validatePackageName(name) {
108
+ const trimmed = name.trim();
109
+ if (trimmed.length === 0) {
110
+ return "must not be empty";
111
+ }
112
+ if (trimmed.length > 214) {
113
+ return "must be 214 characters or fewer";
114
+ }
115
+ if (trimmed !== trimmed.toLowerCase()) {
116
+ return "must be lowercase";
117
+ }
118
+ if (/\s/.test(trimmed)) {
119
+ return "must not contain spaces";
120
+ }
121
+ if (trimmed.startsWith("@")) {
122
+ const separator = trimmed.indexOf("/");
123
+ if (separator <= 1 || separator === trimmed.length - 1) {
124
+ return "scoped names must be in the form @scope/name";
125
+ }
126
+ if (trimmed.indexOf("/", separator + 1) !== -1) {
127
+ return "scoped names must contain exactly one '/' separator";
128
+ }
129
+ const scope = trimmed.slice(1, separator);
130
+ const pkg = trimmed.slice(separator + 1);
131
+ const scopeError = validatePackageSegment(scope, "scope");
132
+ if (scopeError) {
133
+ return scopeError;
134
+ }
135
+ const nameError = validatePackageSegment(pkg, "name");
136
+ if (nameError) {
137
+ return nameError;
138
+ }
139
+ if (RESERVED_PACKAGE_NAMES.has(pkg)) {
140
+ return `'${pkg}' is a reserved package name`;
141
+ }
142
+ return;
143
+ }
144
+ const segmentError = validatePackageSegment(trimmed, "name");
145
+ if (segmentError) {
146
+ return segmentError;
147
+ }
148
+ if (RESERVED_PACKAGE_NAMES.has(trimmed)) {
149
+ return `'${trimmed}' is a reserved package name`;
150
+ }
151
+ return;
152
+ }
153
+
154
+ export { deriveProjectName, deriveBinName, resolveAuthor, resolveYear, resolvePackageName, validateProjectDirectoryName, isPathWithin, sanitizePackageName, validatePackageName };
@@ -1,4 +1,8 @@
1
1
  // @bun
2
+ import {
3
+ injectSharedConfig,
4
+ rewriteLocalDependencies
5
+ } from "./outfitter-ha89qf8q.js";
2
6
  import {
3
7
  copyTemplateFiles,
4
8
  getTemplatesDir
@@ -6,10 +10,6 @@ import {
6
10
  import {
7
11
  addBlocks
8
12
  } from "./outfitter-j833sxws.js";
9
- import {
10
- injectSharedConfig,
11
- rewriteLocalDependencies
12
- } from "./outfitter-ara3djt0.js";
13
13
  import {
14
14
  ScaffoldError
15
15
  } from "./outfitter-8y2dfx6n.js";
@@ -0,0 +1,301 @@
1
+ // @bun
2
+ import {
3
+ getWorkspacePatterns,
4
+ hasWorkspacesField
5
+ } from "./outfitter-1fy7byz5.js";
6
+ import {
7
+ validatePackageName
8
+ } from "./outfitter-4q1zfmvc.js";
9
+ import {
10
+ resolveStructuredOutputMode
11
+ } from "./outfitter-7r12fj7f.js";
12
+
13
+ // apps/outfitter/src/commands/doctor.ts
14
+ import { existsSync, readFileSync } from "fs";
15
+ import { join, resolve } from "path";
16
+ import { output } from "@outfitter/cli";
17
+ import { createTheme } from "@outfitter/tui/render";
18
+ var MIN_BUN_VERSION = "1.3.6";
19
+ function checkBunVersion() {
20
+ const required = MIN_BUN_VERSION;
21
+ const current = Bun.version;
22
+ const passed = Bun.semver.satisfies(current, `>=${required}`);
23
+ return {
24
+ passed,
25
+ version: current,
26
+ required,
27
+ ...passed ? {} : {
28
+ error: `Bun version ${current} is below minimum required ${required}`
29
+ }
30
+ };
31
+ }
32
+ function readPackageJson(cwd) {
33
+ const packageJsonPath = join(cwd, "package.json");
34
+ if (!existsSync(packageJsonPath)) {
35
+ return { exists: false };
36
+ }
37
+ try {
38
+ const content = readFileSync(packageJsonPath, "utf-8");
39
+ return {
40
+ exists: true,
41
+ parsed: JSON.parse(content)
42
+ };
43
+ } catch (error) {
44
+ const message = error instanceof Error ? error.message : "Unknown error";
45
+ return {
46
+ exists: true,
47
+ parseError: message
48
+ };
49
+ }
50
+ }
51
+ function checkPackageJson(packageJson) {
52
+ if (!packageJson.exists) {
53
+ return {
54
+ passed: false,
55
+ error: "package.json not found in current directory"
56
+ };
57
+ }
58
+ if (packageJson.parseError) {
59
+ return {
60
+ passed: false,
61
+ error: `package.json is invalid JSON: ${packageJson.parseError}`
62
+ };
63
+ }
64
+ const parsed = packageJson.parsed;
65
+ if (!parsed) {
66
+ return {
67
+ passed: false,
68
+ error: "package.json is invalid JSON"
69
+ };
70
+ }
71
+ if (typeof parsed["name"] !== "string" || parsed["name"].length === 0) {
72
+ return {
73
+ passed: false,
74
+ error: "package.json is missing required 'name' field"
75
+ };
76
+ }
77
+ if (typeof parsed["version"] !== "string" || parsed["version"].length === 0) {
78
+ return {
79
+ passed: false,
80
+ error: "package.json is missing required 'version' field"
81
+ };
82
+ }
83
+ const packageName = parsed["name"];
84
+ const packageVersion = parsed["version"];
85
+ const invalidPackageName = validatePackageName(packageName);
86
+ if (invalidPackageName) {
87
+ return {
88
+ passed: false,
89
+ name: packageName,
90
+ version: packageVersion,
91
+ error: `package.json has invalid package name '${packageName}': ${invalidPackageName}`
92
+ };
93
+ }
94
+ return {
95
+ passed: true,
96
+ name: packageName,
97
+ version: packageVersion
98
+ };
99
+ }
100
+ function checkDependencies(cwd, packageJson, rootCwd) {
101
+ if (!packageJson.exists) {
102
+ return { passed: true, count: 0 };
103
+ }
104
+ const parsed = packageJson.parsed;
105
+ if (!parsed) {
106
+ return { passed: true, count: 0 };
107
+ }
108
+ const dependencies = parsed["dependencies"];
109
+ const devDependencies = parsed["devDependencies"];
110
+ const allDeps = [
111
+ ...Object.keys(dependencies ?? {}),
112
+ ...Object.keys(devDependencies ?? {})
113
+ ];
114
+ if (allDeps.length === 0) {
115
+ return { passed: true, count: 0 };
116
+ }
117
+ const nodeModulesPath = join(cwd, "node_modules");
118
+ const rootNodeModulesPath = rootCwd ? join(rootCwd, "node_modules") : null;
119
+ if (!(existsSync(nodeModulesPath) || rootNodeModulesPath && existsSync(rootNodeModulesPath))) {
120
+ return {
121
+ passed: false,
122
+ count: allDeps.length,
123
+ error: "node_modules not found. Run 'bun install' to install dependencies."
124
+ };
125
+ }
126
+ const missing = [];
127
+ for (const dep of allDeps) {
128
+ const localDepPath = join(nodeModulesPath, dep);
129
+ if (existsSync(localDepPath)) {
130
+ continue;
131
+ }
132
+ if (rootNodeModulesPath && existsSync(join(rootNodeModulesPath, dep))) {
133
+ continue;
134
+ }
135
+ missing.push(dep);
136
+ }
137
+ if (missing.length > 0) {
138
+ return {
139
+ passed: false,
140
+ count: allDeps.length,
141
+ missing,
142
+ error: `Missing dependencies: ${missing.join(", ")}. Run 'bun install' to install.`
143
+ };
144
+ }
145
+ return { passed: true, count: allDeps.length };
146
+ }
147
+ function checkConfigFiles(cwd) {
148
+ return {
149
+ tsconfig: existsSync(join(cwd, "tsconfig.json"))
150
+ };
151
+ }
152
+ function checkDirectories(cwd) {
153
+ return {
154
+ src: existsSync(join(cwd, "src"))
155
+ };
156
+ }
157
+ function discoverWorkspaceMemberPaths(cwd, packageJson) {
158
+ const patterns = getWorkspacePatterns(packageJson["workspaces"]);
159
+ const memberPaths = new Set;
160
+ for (const pattern of patterns) {
161
+ const normalizedPattern = pattern.replace(/\/+$/, "");
162
+ const packageJsonPattern = normalizedPattern.endsWith("package.json") ? normalizedPattern : `${normalizedPattern}/package.json`;
163
+ const glob = new Bun.Glob(packageJsonPattern);
164
+ for (const match of glob.scanSync({ cwd, dot: false })) {
165
+ const normalizedMatch = match.replaceAll("\\", "/");
166
+ if (!normalizedMatch.endsWith("/package.json")) {
167
+ continue;
168
+ }
169
+ const path = normalizedMatch.slice(0, -"package.json".length - 1);
170
+ if (path.length > 0) {
171
+ memberPaths.add(path);
172
+ }
173
+ }
174
+ }
175
+ return [...memberPaths].sort();
176
+ }
177
+ function isWorkspaceRoot(packageJson) {
178
+ return packageJson ? hasWorkspacesField(packageJson) : false;
179
+ }
180
+ function runDoctorForCwd(cwd, options) {
181
+ const packageJsonRead = readPackageJson(cwd);
182
+ const wsRoot = isWorkspaceRoot(packageJsonRead.parsed);
183
+ const bunVersion = checkBunVersion();
184
+ const packageJson = checkPackageJson(packageJsonRead);
185
+ const dependencies = checkDependencies(cwd, packageJsonRead, options.rootCwd);
186
+ const configFiles = checkConfigFiles(cwd);
187
+ const directories = checkDirectories(cwd);
188
+ const normalizedConfigFiles = wsRoot ? { ...configFiles, tsconfig: true } : configFiles;
189
+ const normalizedDirectories = wsRoot ? { ...directories, src: true } : directories;
190
+ const checkResults = wsRoot ? [bunVersion.passed, packageJson.passed, dependencies.passed] : [
191
+ bunVersion.passed,
192
+ packageJson.passed,
193
+ dependencies.passed,
194
+ configFiles.tsconfig,
195
+ directories.src
196
+ ];
197
+ const passed = checkResults.filter(Boolean).length;
198
+ const failed = checkResults.length - passed;
199
+ const total = checkResults.length;
200
+ const workspaceMembers = options.includeWorkspaceMembers && wsRoot && packageJsonRead.parsed ? discoverWorkspaceMemberPaths(cwd, packageJsonRead.parsed).map((path) => {
201
+ const memberResult = runDoctorForCwd(join(cwd, path), {
202
+ includeWorkspaceMembers: false,
203
+ rootCwd: cwd
204
+ });
205
+ return {
206
+ path,
207
+ summary: memberResult.summary,
208
+ exitCode: memberResult.exitCode
209
+ };
210
+ }) : undefined;
211
+ return {
212
+ checks: {
213
+ bunVersion,
214
+ packageJson,
215
+ dependencies,
216
+ configFiles: normalizedConfigFiles,
217
+ directories: normalizedDirectories
218
+ },
219
+ summary: { passed, failed, total },
220
+ exitCode: failed > 0 ? 1 : 0,
221
+ ...wsRoot ? { isWorkspaceRoot: true } : {},
222
+ ...wsRoot ? {
223
+ skippedChecks: [
224
+ "checks.configFiles.tsconfig",
225
+ "checks.directories.src"
226
+ ]
227
+ } : {},
228
+ ...workspaceMembers && workspaceMembers.length > 0 ? { workspaceMembers } : {}
229
+ };
230
+ }
231
+ function runDoctor(options) {
232
+ const cwd = resolve(options.cwd);
233
+ return runDoctorForCwd(cwd, { includeWorkspaceMembers: true });
234
+ }
235
+ async function printDoctorResults(result, options) {
236
+ const structuredMode = resolveStructuredOutputMode(options?.mode);
237
+ if (structuredMode) {
238
+ await output(result, { mode: structuredMode });
239
+ return;
240
+ }
241
+ const theme = createTheme();
242
+ const lines = ["", "Outfitter Doctor", "", "=".repeat(50)];
243
+ const bunIcon = result.checks.bunVersion.passed ? theme.success("[PASS]") : theme.error("[FAIL]");
244
+ lines.push(`${bunIcon} Bun Version: ${result.checks.bunVersion.version} (requires ${result.checks.bunVersion.required})`);
245
+ if (result.checks.bunVersion.error) {
246
+ lines.push(` ${theme.muted(result.checks.bunVersion.error)}`);
247
+ }
248
+ const pkgIcon = result.checks.packageJson.passed ? theme.success("[PASS]") : theme.error("[FAIL]");
249
+ lines.push(`${pkgIcon} package.json`);
250
+ if (result.checks.packageJson.error) {
251
+ lines.push(` ${theme.muted(result.checks.packageJson.error)}`);
252
+ } else if (result.checks.packageJson.name) {
253
+ lines.push(` ${theme.muted(`${result.checks.packageJson.name}@${result.checks.packageJson.version}`)}`);
254
+ }
255
+ const depsIcon = result.checks.dependencies.passed ? theme.success("[PASS]") : theme.error("[FAIL]");
256
+ lines.push(`${depsIcon} Dependencies`);
257
+ if (result.checks.dependencies.error) {
258
+ lines.push(` ${theme.muted(result.checks.dependencies.error)}`);
259
+ } else if (result.checks.dependencies.count !== undefined) {
260
+ lines.push(` ${theme.muted(`${result.checks.dependencies.count} dependencies installed`)}`);
261
+ }
262
+ if (result.isWorkspaceRoot) {
263
+ lines.push(`${theme.muted("[SKIP]")} tsconfig.json`);
264
+ lines.push(` ${theme.muted("Not checked at workspace root")}`);
265
+ } else {
266
+ const tsconfigIcon = result.checks.configFiles.tsconfig ? theme.success("[PASS]") : theme.warning("[WARN]");
267
+ lines.push(`${tsconfigIcon} tsconfig.json`);
268
+ }
269
+ if (result.isWorkspaceRoot) {
270
+ lines.push(`${theme.muted("[SKIP]")} src/ directory`);
271
+ lines.push(` ${theme.muted("Not checked at workspace root")}`);
272
+ } else {
273
+ const srcIcon = result.checks.directories.src ? theme.success("[PASS]") : theme.warning("[WARN]");
274
+ lines.push(`${srcIcon} src/ directory`);
275
+ }
276
+ if (result.workspaceMembers && result.workspaceMembers.length > 0) {
277
+ lines.push("", "Members:");
278
+ for (const member of result.workspaceMembers) {
279
+ const memberIcon = member.exitCode === 0 ? theme.success("[PASS]") : theme.warning("[WARN]");
280
+ lines.push(` ${memberIcon} ${member.path} ${member.summary.passed}/${member.summary.total} checks passed`);
281
+ }
282
+ }
283
+ lines.push("", "=".repeat(50));
284
+ const summaryColor = result.exitCode === 0 ? theme.success : theme.error;
285
+ lines.push(summaryColor(`${result.summary.passed}/${result.summary.total} checks passed`));
286
+ if (result.exitCode !== 0) {
287
+ lines.push("", theme.muted("Run 'outfitter doctor' after fixing issues to verify."));
288
+ }
289
+ await output(lines, { mode: "human" });
290
+ }
291
+ function doctorCommand(program) {
292
+ program.command("doctor").description("Validate environment and dependencies").action(async (_flags, command) => {
293
+ const resolvedFlags = command.optsWithGlobals();
294
+ const outputOptions = resolvedFlags.json ? { mode: "json" } : undefined;
295
+ const result = runDoctor({ cwd: process.cwd() });
296
+ await printDoctorResults(result, outputOptions);
297
+ process.exit(result.exitCode);
298
+ });
299
+ }
300
+
301
+ export { runDoctor, printDoctorResults, doctorCommand };