Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ce6cbdb
feat(dart_frog_cli): support for Dart workspaces
felangel Aug 8, 2025
0695bdb
chore: update barrel
felangel Aug 8, 2025
c1978a3
fix: use workspace lockfile in production build
felangel Aug 9, 2025
be60632
chore: adjust comment
felangel Aug 9, 2025
47779f4
chore: regenerate bundle
felangel Aug 9, 2025
46517a3
chore: more doc fixes
felangel Aug 9, 2025
d06d4a7
test: fix broken tests and add missing unit tests
felangel Aug 12, 2025
d34ad3d
test: add more tests
felangel Aug 12, 2025
63fb96b
more test fixes
felangel Aug 12, 2025
acff678
cleanup
felangel Aug 12, 2025
b390948
fix coverage
felangel Aug 12, 2025
e3db437
chore: coverage
felangel Aug 12, 2025
359538e
revert post_gen changes
felangel Aug 12, 2025
eeed72b
ci: use updated workflow
felangel Aug 12, 2025
60031d5
revert ci changes
felangel Aug 12, 2025
08478fd
chore: regenerate bundle
felangel Aug 12, 2025
b080916
refactor: go back to lockfiles
felangel Aug 27, 2025
29ed861
cleanup
felangel Aug 27, 2025
b7adf48
chore: regen bundle
felangel Aug 27, 2025
22cc6ff
minimize diff
felangel Aug 27, 2025
3d26766
regen bundle
felangel Aug 27, 2025
a3b8d81
fix analysis warning
felangel Aug 27, 2025
ff77d3b
various fixes
felangel Aug 29, 2025
be5d71d
regen bundles
felangel Aug 29, 2025
c64b055
refactor: simplify approach
felangel Sep 4, 2025
c44d129
chore: remove unnecessary dep
felangel Sep 4, 2025
64f1c28
regen bundle
felangel Sep 4, 2025
776f154
refactor: improve pubspec_overrides handling
felangel Sep 4, 2025
8ada490
more edge case fixes
felangel Sep 4, 2025
b3ccf5b
cleanup
felangel Sep 4, 2025
fa0834a
Merge branch 'main' into feat/workspaces
felangel Sep 4, 2025
6c110f4
chore: use `VoidCallback`
felangel Sep 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ COPY ./pubspec_overrides.yaml ./pubspec_overrides.yaml
{{/hasExternalDependencies}}
# Resolve app dependencies.
COPY pubspec.* ./
COPY pubspec_overrides.yaml* ./
RUN dart pub get

# Copy app source code and AOT compile it.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
export 'src/adjust_relative_pubspec_imports.dart';
export 'src/copy_workspace_pubspec_lock.dart';
export 'src/create_bundle.dart';
export 'src/create_external_packages_folder.dart';
export 'src/dart_pub_get.dart';
export 'src/disable_workspace_resolution.dart';
export 'src/exit_overrides.dart';
export 'src/get_internal_path_dependencies.dart';
export 'src/get_pubspec_lock.dart';
export 'src/uses_workspace_resolution.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'dart:io';
import 'package:mason/mason.dart';
import 'package:path/path.dart' as path;
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:yaml_edit/yaml_edit.dart';

/// Fixes up the relative path imports in the build/pubspec.yaml
void adjustRelativePubspecImports(
HookContext context, {
required String buildDirectory,
required void Function(int exitCode) exit,
}) {
final pubspecFile = File(path.join(buildDirectory, 'pubspec.yaml'));
if (!pubspecFile.existsSync()) {
context.logger.err('Unable to find ${pubspecFile.path}');
exit(1);
}

final String contents;
final Pubspec pubspec;
try {
contents = pubspecFile.readAsStringSync();
pubspec = Pubspec.parse(contents);
} on Exception catch (e) {
context.logger.err('$e');
return exit(1);
}

final yamlEditor = YamlEditor(contents);
for (final dependency in pubspec.dependencies.entries) {
final dep = dependency.value;
if (dep is! PathDependency) continue;
yamlEditor.update(
['dependencies', dependency.key, 'path'],
path.relative(dep.path, from: buildDirectory),
);
}

pubspecFile.writeAsStringSync(yamlEditor.toString());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'dart:io';
import 'package:mason/mason.dart';
import 'package:path/path.dart' as path;
import 'package:pubspec_parse/pubspec_parse.dart';

/// Copies the pubspec.lock from the workspace root into the build directory
/// in order to ensure the production build uses the exact same versions of all
/// dependencies.
void copyWorkspacePubspecLock(
HookContext context, {
required String buildDirectory,
required String workingDirectory,
required void Function(int exitCode) exit,
}) {
final workspaceRoot = _getWorkspaceRoot(workingDirectory);
if (workspaceRoot == null) {
context.logger.err(
'Unable to determine workspace root for $workingDirectory',
);
return exit(1);
}

final pubspecLockFile = File(path.join(workspaceRoot.path, 'pubspec.lock'));
if (!pubspecLockFile.existsSync()) return;

try {
pubspecLockFile.copySync(path.join(buildDirectory, 'pubspec.lock'));
} on Exception catch (error) {
context.logger.err('$error');
return exit(1);
}
}

/// Returns the root directory of the nearest Dart workspace.
Directory? _getWorkspaceRoot(String workingDirectory) {
final file = _findNearestAncestor(
where: (path) => _getWorkspaceRootPubspecYaml(cwd: Directory(path)),
cwd: Directory(workingDirectory),
);
if (file == null || !file.existsSync()) return null;
return Directory(path.dirname(file.path));
}

/// The workspace root `pubspec.yaml` file for this project.
File? _getWorkspaceRootPubspecYaml({required Directory cwd}) {
try {
final pubspecYamlFile = File(path.join(cwd.path, 'pubspec.yaml'));
if (!pubspecYamlFile.existsSync()) return null;
final pubspec = Pubspec.parse(pubspecYamlFile.readAsStringSync());
if (pubspec.workspace?.isEmpty ?? true) return null;
return pubspecYamlFile;
} on Exception {
return null;
}
}

/// Finds nearest ancestor file
/// relative to the [cwd] that satisfies [where].
File? _findNearestAncestor({
required File? Function(String path) where,
required Directory cwd,
}) {
Directory? prev;
var dir = cwd;
while (prev?.path != dir.path) {
final file = where(dir.path);
if (file?.existsSync() ?? false) return file;
prev = dir;
dir = dir.parent;
}
return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,29 @@ Future<List<String>> createExternalPackagesFolder({
}) async {
final pathResolver = path.context;
final pubspecLock = await getPubspecLock(
projectDirectory.path,
buildDirectory.path,
pathContext: path.context,
);

final externalPathDependencies = pubspecLock.packages
.map(
(dependency) {
final pathDescription = dependency.pathDescription;
if (pathDescription == null) {
return null;
}
if (pathDescription == null) return null;

final isExternal = !pathResolver.isWithin('', pathDescription.path);
if (!isExternal) return null;

return _ExternalPathDependency(
name: dependency.name,
path: path.join(projectDirectory.path, pathDescription.path),
path: path.join(buildDirectory.path, pathDescription.path),
);
},
)
.whereType<_ExternalPathDependency>()
.toList();

if (externalPathDependencies.isEmpty) {
return [];
}
if (externalPathDependencies.isEmpty) return [];

final packagesDirectory = Directory(
pathResolver.join(
Expand All @@ -51,34 +47,39 @@ Future<List<String>> createExternalPackagesFolder({

final copiedExternalPathDependencies = await Future.wait(
externalPathDependencies.map(
(externalPathDependency) => externalPathDependency.copyTo(
copyPath: copyPath,
targetDirectory: Directory(
pathResolver.join(
packagesDirectory.path,
externalPathDependency.name,
(externalPathDependency) async {
final copy = await externalPathDependency.copyTo(
copyPath: copyPath,
targetDirectory: Directory(
pathResolver.join(
packagesDirectory.path,
externalPathDependency.name,
),
),
),
),
);
overrideResolutionInPubspecOverrides(copy.path);
return copy;
},
),
);

await File(
pathResolver.join(
buildDirectory.path,
'pubspec_overrides.yaml',
),
).writeAsString('''
File(
pathResolver.join(buildDirectory.path, 'pubspec_overrides.yaml'),
).writeAsStringSync(
'''

dependency_overrides:
${copiedExternalPathDependencies.map(
(dependency) {
final name = dependency.name;
final path =
pathResolver.relative(dependency.path, from: buildDirectory.path);
return ' $name:\n path: $path';
},
).join('\n')}
''');
(dependency) {
final name = dependency.name;
final path =
pathResolver.relative(dependency.path, from: buildDirectory.path);
return ' $name:\n path: $path';
},
).join('\n')}
''',
mode: FileMode.append,
);

return copiedExternalPathDependencies
.map((dependency) => dependency.path)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'dart:io';
import 'package:mason/mason.dart';
import 'package:path/path.dart' as path;

/// Opts out of dart workspaces until we can generate per package lockfiles.
/// https://github.com/dart-lang/pub/issues/4594
void disableWorkspaceResolution(
HookContext context, {
required String buildDirectory,
required void Function(int exitCode) exit,
}) {
try {
overrideResolutionInPubspecOverrides(buildDirectory);
} on Exception catch (e) {
context.logger.err('$e');
exit(1);
}
}

void overrideResolutionInPubspecOverrides(String directory) {
final pubspecOverrides = File(
path.join(directory, 'pubspec_overrides.yaml'),
);

if (pubspecOverrides.existsSync()) {
return pubspecOverrides.writeAsStringSync(
'\nresolution: null\n',
mode: FileMode.append,
);
}

pubspecOverrides
..createSync(recursive: true)
..writeAsStringSync('resolution: null');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'dart:io';
import 'package:mason/mason.dart';
import 'package:path/path.dart' as path;
import 'package:pubspec_parse/pubspec_parse.dart';

/// Determines whether the project in the provided [workingDirectory]
/// is configured to use `resolution: workspace`.
bool usesWorkspaceResolution(
HookContext context, {
required String workingDirectory,
required void Function(int exitCode) exit,
}) {
final pubspecFile = File(path.join(workingDirectory, 'pubspec.yaml'));
if (!pubspecFile.existsSync()) return false;

final Pubspec pubspec;
try {
pubspec = Pubspec.parse(pubspecFile.readAsStringSync());
} on Exception catch (e) {
context.logger.err('$e');
exit(1);
return false;
}

return pubspec.resolution == 'workspace';
}
11 changes: 0 additions & 11 deletions bricks/dart_frog_prod_server/hooks/post_gen.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:io' as io;

import 'package:dart_frog_prod_server_hooks/dart_frog_prod_server_hooks.dart';
import 'package:mason/mason.dart' show HookContext, lightCyan;
import 'package:path/path.dart' as path;

Expand All @@ -10,19 +9,9 @@ Future<void> run(HookContext context) => postGen(context);
Future<void> postGen(
HookContext context, {
io.Directory? directory,
ProcessRunner runProcess = io.Process.run,
void Function(int exitCode) exit = defaultExit,
}) async {
final projectDirectory = directory ?? io.Directory.current;
final buildDirectoryPath = path.join(projectDirectory.path, 'build');

await dartPubGet(
context,
workingDirectory: buildDirectoryPath,
runProcess: runProcess,
exit: exit,
);

final relativeBuildPath = path.relative(buildDirectoryPath);
context.logger
..info('')
Expand Down
Loading
Loading