@baseplate-dev/project-builder-cli 0.3.7 → 0.4.0

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.
@@ -12,12 +12,16 @@ export function addDiffCommand(program) {
12
12
  .option('--compact', 'Show compact diff format instead of unified diff')
13
13
  .option('--package <packages...>', 'Filter by specific package names')
14
14
  .option('--include <patterns...>', 'Filter files by glob patterns')
15
+ .option('--fail-on-differences', 'Fail the command if differences are found')
15
16
  .action(async (project, options) => {
16
17
  const resolvedProject = await resolveProject(project);
17
18
  const context = await createServiceActionContext(resolvedProject);
18
- await invokeServiceActionAsCli(diffProjectAction, {
19
+ const result = await invokeServiceActionAsCli(diffProjectAction, {
19
20
  project: resolvedProject.name,
20
21
  ...options,
21
22
  }, context);
23
+ if (result.hasDifferences && options.failOnDifferences) {
24
+ throw new Error('Differences found between generated output and project');
25
+ }
22
26
  });
23
27
  }
@@ -32,9 +32,7 @@ async function handleListProjects(options) {
32
32
  const projects = [...projectMap.values()].map((project) => ({
33
33
  name: project.name,
34
34
  path: project.path,
35
- isExample: project.isExample,
36
- description: project.packageJson.description ?? null,
37
- version: project.packageJson.version ?? null,
35
+ isInternalExample: project.isInternalExample,
38
36
  }));
39
37
  console.info(JSON.stringify(projects, null, 2));
40
38
  }
@@ -44,7 +42,7 @@ async function handleListProjects(options) {
44
42
  const examples = [];
45
43
  const regular = [];
46
44
  for (const project of projectMap.values()) {
47
- if (project.isExample) {
45
+ if (project.isInternalExample) {
48
46
  examples.push(project);
49
47
  }
50
48
  else {
@@ -56,9 +54,6 @@ async function handleListProjects(options) {
56
54
  console.info('📦 Projects:');
57
55
  for (const project of regular) {
58
56
  console.info(` ${project.name}`);
59
- if (typeof project.packageJson.description === 'string') {
60
- console.info(` ${project.packageJson.description}`);
61
- }
62
57
  console.info(` Path: ${project.path}`);
63
58
  console.info();
64
59
  }
@@ -68,9 +63,6 @@ async function handleListProjects(options) {
68
63
  console.info('📚 Examples:');
69
64
  for (const project of examples) {
70
65
  console.info(` ${project.name}`);
71
- if (typeof project.packageJson.description === 'string') {
72
- console.info(` ${project.packageJson.description}`);
73
- }
74
66
  console.info(` Path: ${project.path}`);
75
67
  console.info();
76
68
  }
@@ -1,8 +1,6 @@
1
+ import { invokeServiceActionAsCli, snapshotAddAction, snapshotRemoveAction, snapshotSaveAction, snapshotShowAction, } from '@baseplate-dev/project-builder-server/actions';
1
2
  import { confirm } from '@inquirer/prompts';
2
- import { createSchemaParserContext } from '#src/services/schema-parser-context.js';
3
- import { getUserConfig } from '#src/services/user-config.js';
4
- import { expandPathWithTilde } from '#src/utils/path.js';
5
- import { resolveProject } from '#src/utils/project-resolver.js';
3
+ import { createServiceActionContext } from '#src/utils/create-service-action-context.js';
6
4
  import { logger } from '../services/logger.js';
7
5
  /**
8
6
  * Adds snapshot management commands to the program.
@@ -20,26 +18,13 @@ export function addSnapshotCommand(program) {
20
18
  .option('--snapshot-dir <directory>', 'Snapshot directory', '.baseplate-snapshot')
21
19
  .action(async (project, app, files, options) => {
22
20
  try {
23
- const { addFilesToSnapshot } = await import('@baseplate-dev/project-builder-server');
24
- let resolvedDirectory;
25
- if (project.includes('/') || project.includes('\\')) {
26
- // It's a path, expand it
27
- resolvedDirectory = expandPathWithTilde(project);
28
- }
29
- else {
30
- // It's a project name, resolve it
31
- const projectInfo = await resolveProject(project);
32
- resolvedDirectory = projectInfo.path;
33
- logger.info(`Running snapshot add for project: ${projectInfo.name}`);
34
- }
35
- const context = await createSchemaParserContext(resolvedDirectory);
36
- await addFilesToSnapshot(files, !!options.deleted, {
37
- projectDirectory: resolvedDirectory,
38
- snapshotDirectory: options.snapshotDir,
39
- appName: app,
40
- context,
41
- logger,
42
- });
21
+ const context = await createServiceActionContext();
22
+ await invokeServiceActionAsCli(snapshotAddAction, {
23
+ project,
24
+ app,
25
+ files,
26
+ deleted: options.deleted,
27
+ }, context);
43
28
  }
44
29
  catch (error) {
45
30
  logger.error('Failed to add files to snapshot:', error);
@@ -53,26 +38,13 @@ export function addSnapshotCommand(program) {
53
38
  .option('--snapshot-dir <directory>', 'Snapshot directory', '.baseplate-snapshot')
54
39
  .action(async (project, app, files, options) => {
55
40
  try {
56
- const { removeFilesFromSnapshot } = await import('@baseplate-dev/project-builder-server');
57
- let resolvedDirectory;
58
- if (project.includes('/') || project.includes('\\')) {
59
- // It's a path, expand it
60
- resolvedDirectory = expandPathWithTilde(project);
61
- }
62
- else {
63
- // It's a project name, resolve it
64
- const projectInfo = await resolveProject(project);
65
- resolvedDirectory = projectInfo.path;
66
- logger.info(`Running snapshot remove for project: ${projectInfo.name}`);
67
- }
68
- const context = await createSchemaParserContext(resolvedDirectory);
69
- await removeFilesFromSnapshot(files, {
70
- projectDirectory: resolvedDirectory,
41
+ const context = await createServiceActionContext();
42
+ await invokeServiceActionAsCli(snapshotRemoveAction, {
43
+ project,
44
+ app,
45
+ files,
71
46
  snapshotDirectory: options.snapshotDir,
72
- appName: app,
73
- context,
74
- logger,
75
- });
47
+ }, context);
76
48
  }
77
49
  catch (error) {
78
50
  logger.error('Failed to remove files from snapshot:', error);
@@ -85,46 +57,23 @@ export function addSnapshotCommand(program) {
85
57
  .description('Save snapshot of current differences (overwrites existing snapshot)')
86
58
  .option('--snapshot-dir <directory>', 'Snapshot directory', '.baseplate-snapshot')
87
59
  .action(async (project, app, options) => {
88
- try {
89
- const { createSnapshotForProject } = await import('@baseplate-dev/project-builder-server');
90
- let resolvedDirectory;
91
- if (project.includes('/') || project.includes('\\')) {
92
- // It's a path, expand it
93
- resolvedDirectory = expandPathWithTilde(project);
94
- }
95
- else {
96
- // It's a project name, resolve it
97
- const projectInfo = await resolveProject(project);
98
- resolvedDirectory = projectInfo.path;
99
- logger.info(`Running snapshot save for project: ${projectInfo.name}`);
100
- }
101
- const context = await createSchemaParserContext(resolvedDirectory);
102
- const userConfig = await getUserConfig();
103
- // Confirm with user before overwriting existing snapshot
104
- console.warn('⚠️ This will overwrite any existing snapshot for this app.');
105
- console.info('Use granular commands (snapshot add/remove) for safer updates.');
106
- const proceed = await confirm({
107
- message: 'Are you sure you want to overwrite the existing snapshot?',
108
- default: false,
109
- });
110
- if (!proceed) {
111
- logger.info('Aborted snapshot save.');
112
- return;
113
- }
114
- await createSnapshotForProject({
115
- projectDirectory: resolvedDirectory,
116
- app,
117
- logger,
118
- context,
119
- userConfig,
120
- snapshotDir: options.snapshotDir,
121
- });
122
- logger.info('✅ Snapshot saved successfully');
123
- }
124
- catch (error) {
125
- logger.error('Failed to save snapshot:', error);
126
- throw error;
60
+ // Confirm with user before overwriting existing snapshot
61
+ console.warn('⚠️ This will overwrite any existing snapshot for this app.');
62
+ console.info('Use granular commands (snapshot add/remove) for safer updates.');
63
+ const proceed = await confirm({
64
+ message: 'Are you sure you want to overwrite the existing snapshot?',
65
+ default: false,
66
+ });
67
+ if (!proceed) {
68
+ logger.info('Aborted snapshot save.');
69
+ return;
127
70
  }
71
+ const context = await createServiceActionContext();
72
+ await invokeServiceActionAsCli(snapshotSaveAction, {
73
+ project,
74
+ app,
75
+ snapshotDirectory: options.snapshotDir,
76
+ }, context);
128
77
  });
129
78
  // snapshot show command
130
79
  snapshotCommand
@@ -132,31 +81,11 @@ export function addSnapshotCommand(program) {
132
81
  .description('Show current snapshot contents')
133
82
  .option('--snapshot-dir <directory>', 'Snapshot directory', '.baseplate-snapshot')
134
83
  .action(async (project, app, options) => {
135
- try {
136
- const { listSnapshotContents } = await import('@baseplate-dev/project-builder-server');
137
- let resolvedDirectory;
138
- if (project.includes('/') || project.includes('\\')) {
139
- // It's a path, expand it
140
- resolvedDirectory = expandPathWithTilde(project);
141
- }
142
- else {
143
- // It's a project name, resolve it
144
- const projectInfo = await resolveProject(project);
145
- resolvedDirectory = projectInfo.path;
146
- logger.info(`Running snapshot show for project: ${projectInfo.name}`);
147
- }
148
- const context = await createSchemaParserContext(resolvedDirectory);
149
- await listSnapshotContents({
150
- projectDirectory: resolvedDirectory,
151
- appName: app,
152
- snapshotDirectory: options.snapshotDir,
153
- context,
154
- logger,
155
- });
156
- }
157
- catch (error) {
158
- logger.error('Failed to show snapshot contents:', error);
159
- throw error;
160
- }
84
+ const context = await createServiceActionContext();
85
+ await invokeServiceActionAsCli(snapshotShowAction, {
86
+ project,
87
+ app,
88
+ snapshotDirectory: options.snapshotDir,
89
+ }, context);
161
90
  });
162
91
  }
@@ -1,2 +1,3 @@
1
- import type { ServiceActionContext, ServiceActionProject } from '@baseplate-dev/project-builder-server/actions';
2
- export declare function createServiceActionContext(project?: ServiceActionProject): Promise<ServiceActionContext>;
1
+ import type { ProjectInfo } from '@baseplate-dev/project-builder-lib';
2
+ import type { ServiceActionContext } from '@baseplate-dev/project-builder-server/actions';
3
+ export declare function createServiceActionContext(project?: ProjectInfo): Promise<ServiceActionContext>;
@@ -2,14 +2,17 @@ import { getDefaultPlugins } from '@baseplate-dev/project-builder-common';
2
2
  import { logger } from '#src/services/logger.js';
3
3
  import { getUserConfig } from '#src/services/user-config.js';
4
4
  import { listProjects } from './list-projects.js';
5
+ import { getPackageVersion } from './version.js';
5
6
  export async function createServiceActionContext(project) {
6
7
  const projects = project ? [project] : await listProjects({});
7
8
  const plugins = await getDefaultPlugins(logger);
8
9
  const userConfig = await getUserConfig();
10
+ const cliVersion = await getPackageVersion();
9
11
  return {
10
12
  projects,
11
13
  logger,
12
14
  userConfig,
13
15
  plugins,
16
+ cliVersion,
14
17
  };
15
18
  }
@@ -1,4 +1,4 @@
1
- import type { ServiceActionProject } from '@baseplate-dev/project-builder-server/actions';
1
+ import type { ProjectInfo } from '@baseplate-dev/project-builder-lib';
2
2
  /**
3
3
  * Lists all available projects by searching through configured directories.
4
4
  *
@@ -25,7 +25,7 @@ import type { ServiceActionProject } from '@baseplate-dev/project-builder-server
25
25
  */
26
26
  export declare function listProjects({ additionalDirectories, }: {
27
27
  additionalDirectories?: string[];
28
- }): Promise<ServiceActionProject[]>;
28
+ }): Promise<ProjectInfo[]>;
29
29
  /**
30
30
  * Resolves a project by name or directory path.
31
31
  *
@@ -55,7 +55,7 @@ export declare function listProjects({ additionalDirectories, }: {
55
55
  * const project = await resolveProject('~/projects/my-project');
56
56
  * ```
57
57
  */
58
- export declare function resolveProject(projectNameOrDirectory?: string | undefined): Promise<ServiceActionProject>;
58
+ export declare function resolveProject(projectNameOrDirectory?: string | undefined): Promise<ProjectInfo>;
59
59
  /**
60
60
  * Lists all example projects by searching through example directories only.
61
61
  *
@@ -70,4 +70,4 @@ export declare function resolveProject(projectNameOrDirectory?: string | undefin
70
70
  * const exampleProjects = await getExampleProjects();
71
71
  * ```
72
72
  */
73
- export declare function getExampleProjects(): Promise<ServiceActionProject[]>;
73
+ export declare function getExampleProjects(): Promise<ProjectInfo[]>;
@@ -1,15 +1,13 @@
1
1
  /**
2
2
  * Information about a discovered project
3
3
  */
4
- export interface ProjectInfo {
4
+ export interface DiscoveredProjectInfo {
5
5
  /** Project name from package.json */
6
6
  name: string;
7
7
  /** Absolute path to the project directory */
8
8
  path: string;
9
- /** Parsed package.json content */
10
- packageJson: Record<string, unknown>;
11
- /** Whether this project is from the examples directory */
12
- isExample: boolean;
9
+ /** Whether this project is an internal example project */
10
+ isInternalExample: boolean;
13
11
  }
14
12
  /**
15
13
  * Options for resolving projects
@@ -26,10 +24,10 @@ interface ResolveProjectsOptions {
26
24
  * Resolves all available projects based on the provided options.
27
25
  *
28
26
  * @param options - Configuration for project resolution
29
- * @returns Map of project name to ProjectInfo
27
+ * @returns Map of project name to DiscoveredProjectInfo
30
28
  * @throws Error if duplicate project names are found or if project loading fails
31
29
  */
32
- export declare function resolveProjects(options?: ResolveProjectsOptions): Promise<Map<string, ProjectInfo>>;
30
+ export declare function resolveProjects(options?: ResolveProjectsOptions): Promise<Map<string, DiscoveredProjectInfo>>;
33
31
  /**
34
32
  * Resolves a single project by name or path.
35
33
  *
@@ -37,19 +35,19 @@ export declare function resolveProjects(options?: ResolveProjectsOptions): Promi
37
35
  * @returns ProjectInfo for the resolved project
38
36
  * @throws Error if project cannot be resolved or loaded
39
37
  */
40
- export declare function resolveProject(nameOrPath: string): Promise<ProjectInfo>;
38
+ export declare function resolveProject(nameOrPath: string): Promise<DiscoveredProjectInfo>;
41
39
  /**
42
40
  * Gets project directories from resolved projects.
43
41
  *
44
42
  * @param projectMap - Map of resolved projects
45
43
  * @returns Array of absolute directory paths
46
44
  */
47
- export declare function getProjectDirectories(projectMap: Map<string, ProjectInfo>): string[];
45
+ export declare function getProjectDirectories(projectMap: Map<string, DiscoveredProjectInfo>): string[];
48
46
  /**
49
47
  * Gets project names from resolved projects.
50
48
  *
51
49
  * @param projectMap - Map of resolved projects
52
50
  * @returns Array of project names
53
51
  */
54
- export declare function getProjectNames(projectMap: Map<string, ProjectInfo>): string[];
52
+ export declare function getProjectNames(projectMap: Map<string, DiscoveredProjectInfo>): string[];
55
53
  export {};
@@ -1,12 +1,14 @@
1
- import fs from 'node:fs/promises';
1
+ import { isExampleProject } from '@baseplate-dev/project-builder-server/actions';
2
+ import { readJsonWithSchema } from '@baseplate-dev/utils/node';
2
3
  import path from 'node:path';
4
+ import z from 'zod';
3
5
  import { findExamplesDirectories } from './find-examples-directories.js';
4
6
  import { expandPathWithTilde } from './path.js';
5
7
  /**
6
8
  * Resolves all available projects based on the provided options.
7
9
  *
8
10
  * @param options - Configuration for project resolution
9
- * @returns Map of project name to ProjectInfo
11
+ * @returns Map of project name to DiscoveredProjectInfo
10
12
  * @throws Error if duplicate project names are found or if project loading fails
11
13
  */
12
14
  export async function resolveProjects(options = {}) {
@@ -93,21 +95,24 @@ export async function resolveProject(nameOrPath) {
93
95
  * @throws Error if directory doesn't exist or package.json is invalid
94
96
  */
95
97
  async function loadProjectInfo(directory) {
96
- const packageJsonPath = path.join(directory, 'package.json');
98
+ const definitionPath = path.join(directory, 'baseplate', 'project-definition.json');
97
99
  try {
98
- const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
99
- const packageJson = JSON.parse(packageJsonContent);
100
- const name = packageJson.name;
101
- if (!name || typeof name !== 'string') {
102
- throw new Error('package.json must have a valid "name" field');
100
+ const definition = await readJsonWithSchema(definitionPath, z.object({
101
+ settings: z.object({
102
+ general: z.object({
103
+ name: z.string(),
104
+ }),
105
+ }),
106
+ }));
107
+ const { name } = definition.settings.general;
108
+ if (!name) {
109
+ throw new Error('Project definition must have a valid name in settings.general.name');
103
110
  }
104
- // Determine if this is an example project by checking if it's in examples directory
105
- const isExample = directory.includes('/examples/') || directory.includes('\\examples\\');
111
+ const isInternalExample = await isExampleProject(directory);
106
112
  return {
107
113
  name,
108
114
  path: directory,
109
- packageJson,
110
- isExample,
115
+ isInternalExample,
111
116
  };
112
117
  }
113
118
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baseplate-dev/project-builder-cli",
3
- "version": "0.3.7",
3
+ "version": "0.4.0",
4
4
  "description": "Full-stack CLI builder using Baseplate generators",
5
5
  "keywords": [
6
6
  "cli",
@@ -46,23 +46,23 @@
46
46
  "pino-pretty": "13.0.0",
47
47
  "pkg-dir": "^8.0.0",
48
48
  "zod": "3.25.76",
49
- "@baseplate-dev/project-builder-common": "0.3.7",
50
- "@baseplate-dev/project-builder-lib": "0.3.7",
51
- "@baseplate-dev/project-builder-server": "0.3.7",
52
- "@baseplate-dev/project-builder-web": "0.3.7",
53
- "@baseplate-dev/utils": "0.3.7"
49
+ "@baseplate-dev/project-builder-common": "0.4.0",
50
+ "@baseplate-dev/project-builder-lib": "0.4.0",
51
+ "@baseplate-dev/project-builder-server": "0.4.0",
52
+ "@baseplate-dev/project-builder-web": "0.4.0",
53
+ "@baseplate-dev/utils": "0.4.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@playwright/test": "1.51.0",
56
+ "@playwright/test": "1.56.1",
57
57
  "@types/node": "^22.17.2",
58
58
  "eslint": "9.32.0",
59
59
  "fastify": "5.3.2",
60
60
  "memfs": "4.15.1",
61
61
  "prettier": "3.6.2",
62
- "tsx": "4.19.3",
62
+ "tsx": "4.20.6",
63
63
  "typescript": "5.8.3",
64
64
  "vitest": "3.2.4",
65
- "@baseplate-dev/tools": "0.3.7"
65
+ "@baseplate-dev/tools": "0.4.0"
66
66
  },
67
67
  "engines": {
68
68
  "node": "^22.0.0"
@@ -1,7 +0,0 @@
1
- import type { SchemaParserContext } from '@baseplate-dev/project-builder-lib';
2
- /**
3
- * Creates a schema parser context for the given directory.
4
- * @param directory - The directory to create the schema parser context for.
5
- * @returns A promise that resolves to the schema parser context.
6
- */
7
- export declare function createSchemaParserContext(directory: string): Promise<SchemaParserContext>;
@@ -1,13 +0,0 @@
1
- import { getDefaultPlugins } from '@baseplate-dev/project-builder-common';
2
- import { logger } from './logger.js';
3
- /**
4
- * Creates a schema parser context for the given directory.
5
- * @param directory - The directory to create the schema parser context for.
6
- * @returns A promise that resolves to the schema parser context.
7
- */
8
- export async function createSchemaParserContext(directory) {
9
- // dynamically import to avoid loading the server package unnecessarily
10
- const { createNodeSchemaParserContext } = await import('@baseplate-dev/project-builder-server/plugins');
11
- const builtInPlugins = await getDefaultPlugins(logger);
12
- return createNodeSchemaParserContext(directory, logger, builtInPlugins);
13
- }