Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
12 changes: 10 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ jobs:
analyze-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
with:
channel: stable
- name: Install dependencies
run: flutter pub get
- name: Check formatting
run: dart format --output=none --set-exit-if-changed .
- name: Analyze
run: dart analyze .
- name: Check version is in sync (both packages + version.dart)
Expand All @@ -32,3 +34,9 @@ jobs:
- name: Test insforge_flutter
working-directory: packages/insforge_flutter
run: flutter test
- name: Dry-run publish insforge
working-directory: packages/insforge
run: dart pub publish --dry-run
- name: Dry-run publish insforge_flutter
working-directory: packages/insforge_flutter
run: dart pub publish --dry-run
85 changes: 85 additions & 0 deletions .github/workflows/publish-insforge-flutter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Publish insforge_flutter to pub.dev

on:
push:
tags:
- "insforge_flutter-v[0-9]+.[0-9]+.[0-9]+"
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
- "insforge_flutter-v[0-9]+.[0-9]+.[0-9]+-*"

jobs:
validate:
name: Validate release
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
stable: ${{ steps.version.outputs.stable }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- uses: dart-lang/setup-dart@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2

- name: Validate tag matches package version
id: version
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
version="$(sed -n 's/^version:[[:space:]]*//p' packages/insforge_flutter/pubspec.yaml)"
if [ "$RELEASE_TAG" != "insforge_flutter-v$version" ]; then
echo "Tag $RELEASE_TAG does not match insforge_flutter version $version."
exit 1
fi

echo "version=$version" >> "$GITHUB_OUTPUT"
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "stable=true" >> "$GITHUB_OUTPUT"
else
echo "stable=false" >> "$GITHUB_OUTPUT"
fi

- name: Check lockstep versions
# Run the dependency-free script directly so Dart does not try to
# resolve the root workspace, which also contains a Flutter package.
run: dart tool/set_version.dart --check

- name: Verify matching insforge version is published
env:
RELEASE_VERSION: ${{ steps.version.outputs.version }}
run: |
if ! curl --fail --silent --show-error --retry 5 --retry-delay 10 \
"https://pub.dev/api/packages/insforge/versions/$RELEASE_VERSION" \
>/dev/null; then
echo "insforge $RELEASE_VERSION is not available on pub.dev yet." >&2
echo "Publish insforge-v$RELEASE_VERSION first and wait for it to become available." >&2
exit 1
fi

publish:
name: Publish insforge_flutter
needs: validate
permissions:
id-token: write
uses: dart-lang/setup-dart/.github/workflows/publish.yml@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2
with:
working-directory: packages/insforge_flutter

release:
name: Create stable GitHub Release
if: needs.validate.outputs.stable == 'true'
needs: [validate, publish]
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.ref_name }}
REPOSITORY: ${{ github.repository }}
run: |
if gh release view "$RELEASE_TAG" --repo "$REPOSITORY" >/dev/null 2>&1; then
echo "GitHub Release $RELEASE_TAG already exists"
else
gh release create "$RELEASE_TAG" --repo "$REPOSITORY" --generate-notes --verify-tag
fi
72 changes: 72 additions & 0 deletions .github/workflows/publish-insforge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Publish insforge to pub.dev

on:
push:
tags:
- "insforge-v[0-9]+.[0-9]+.[0-9]+"
- "insforge-v[0-9]+.[0-9]+.[0-9]+-*"

jobs:
validate:
name: Validate release
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
stable: ${{ steps.version.outputs.stable }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- uses: dart-lang/setup-dart@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2

- name: Validate tag matches package version
id: version
env:
RELEASE_TAG: ${{ github.ref_name }}
run: |
version="$(sed -n 's/^version:[[:space:]]*//p' packages/insforge/pubspec.yaml)"
if [ "$RELEASE_TAG" != "insforge-v$version" ]; then
echo "Tag $RELEASE_TAG does not match insforge version $version."
exit 1
fi

if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "stable=true" >> "$GITHUB_OUTPUT"
else
echo "stable=false" >> "$GITHUB_OUTPUT"
fi

- name: Check lockstep versions
# Run the dependency-free script directly so Dart does not try to
# resolve the root workspace, which also contains a Flutter package.
run: dart tool/set_version.dart --check

publish:
name: Publish insforge
needs: validate
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
permissions:
id-token: write
uses: dart-lang/setup-dart/.github/workflows/publish.yml@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2
with:
working-directory: packages/insforge

release:
name: Create stable GitHub Release
if: needs.validate.outputs.stable == 'true'
needs: [validate, publish]
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.ref_name }}
REPOSITORY: ${{ github.repository }}
run: |
if gh release view "$RELEASE_TAG" --repo "$REPOSITORY" >/dev/null 2>&1; then
echo "GitHub Release $RELEASE_TAG already exists"
else
gh release create "$RELEASE_TAG" --repo "$REPOSITORY" --generate-notes --verify-tag
fi
20 changes: 16 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,24 @@ apart.
1. `dart run tool/set_version.dart <new-version>`.
2. Add a `## <new-version>` section to each package's `CHANGELOG.md`.
3. Commit, open a PR, merge to `main`.
4. Tag the release: `git tag v<new-version> && git push --tags`.
5. Publish **in order** (the Flutter package depends on the Dart one):
4. Tag and publish the core package:
```bash
cd packages/insforge && dart pub publish # wait until it's live
cd ../insforge_flutter && dart pub publish
git tag -a insforge-v<new-version> -m "insforge v<new-version>"
git push origin insforge-v<new-version>
```
5. Wait until that exact `insforge` version is visible on pub.dev.
6. Tag and publish the Flutter package from the same release commit:
```bash
git tag -a insforge_flutter-v<new-version> -m "insforge_flutter v<new-version>"
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
git push origin insforge_flutter-v<new-version>
```

Both tags trigger package-specific GitHub Actions workflows that publish through
pub.dev automated publishing (OIDC). No pub.dev token is stored in GitHub. Do
not push the `insforge_flutter` tag before the matching `insforge` version is
available, because the Flutter package depends on it. Each stable package tag
creates a GitHub Release after pub.dev publication succeeds; prerelease tags
publish to pub.dev without creating a GitHub Release.

> Note on 0.x: under SemVer, `^0.1.0` means `>=0.1.0 <0.2.0`. Bumping the minor
> (e.g. `0.1.x → 0.2.0`) is a breaking change, and the tool updates
Expand Down
45 changes: 33 additions & 12 deletions integration_tests/test/database_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,16 @@ void main() {
final rows = await db
.from(_table)
.insert(<Map<String, dynamic>>[
<String, dynamic>{'name': 'batch-a-$ts', 'value': 'batch', 'score': 20},
<String, dynamic>{'name': 'batch-b-$ts', 'value': 'batch', 'score': 30},
<String, dynamic>{
'name': 'batch-a-$ts',
'value': 'batch',
'score': 20,
},
<String, dynamic>{
'name': 'batch-b-$ts',
'value': 'batch',
'score': 30,
},
])
.select()
.execute();
Expand All @@ -159,7 +167,9 @@ void main() {
final tag = 'update-${DateTime.now().microsecondsSinceEpoch}';
final inserted = await db
.from(_table)
.insert(<String, dynamic>{'name': tag, 'value': 'before', 'score': 0})
.insert(
<String, dynamic>{'name': tag, 'value': 'before', 'score': 0},
)
.select()
.execute();
final Object id = inserted.first['id'] as Object;
Expand Down Expand Up @@ -207,8 +217,7 @@ void main() {

await db.from(_table).eq('id', id).delete().execute();

final check =
await db.from(_table).select('id').eq('id', id).execute();
final check = await db.from(_table).select('id').eq('id', id).execute();
expect(check, isEmpty);
});

Expand All @@ -222,14 +231,27 @@ void main() {
final seeded = await db
.from(_table)
.insert(<Map<String, dynamic>>[
<String, dynamic>{'name': '$tag-alpha', 'value': 'hello world', 'score': 10},
<String, dynamic>{'name': '$tag-beta', 'value': 'hello earth', 'score': 20},
<String, dynamic>{'name': '$tag-gamma', 'value': 'goodbye world', 'score': 30},
<String, dynamic>{
'name': '$tag-alpha',
'value': 'hello world',
'score': 10,
},
<String, dynamic>{
'name': '$tag-beta',
'value': 'hello earth',
'score': 20,
},
<String, dynamic>{
'name': '$tag-gamma',
'value': 'goodbye world',
'score': 30,
},
])
.select()
.execute();
seedIds =
seeded.map((Map<String, dynamic> r) => r['id'] as Object).toList();
seedIds = seeded
.map((Map<String, dynamic> r) => r['id'] as Object)
.toList();
insertedIds.addAll(seedIds);
});

Expand Down Expand Up @@ -310,8 +332,7 @@ void main() {
final rows = await db
.from(_table)
.select('name')
.inFilter('name', <Object>['$tag-alpha', '$tag-gamma'])
.execute();
.inFilter('name', <Object>['$tag-alpha', '$tag-gamma']).execute();
expect(rows.length, greaterThanOrEqualTo(2));
});

Expand Down
9 changes: 5 additions & 4 deletions integration_tests/test/storage_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ void main() {
test('uploadAutoKey returns a server-generated key', () async {
if (!bucketAvailable) return;

final content = 'Auto upload – ${DateTime.now().microsecondsSinceEpoch}';
final content =
'Auto upload – ${DateTime.now().microsecondsSinceEpoch}';
final bytes = Uint8List.fromList(utf8.encode(content));

try {
Expand All @@ -121,9 +122,9 @@ void main() {

test('download of a non-existent object throws', () async {
await expectLater(
storage
.from(_bucket)
.download('nonexistent-${DateTime.now().microsecondsSinceEpoch}.txt'),
storage.from(_bucket).download(
'nonexistent-${DateTime.now().microsecondsSinceEpoch}.txt',
),
throwsA(isA<InsforgeHttpException>()),
);
});
Expand Down
4 changes: 3 additions & 1 deletion packages/insforge/.pubignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Files excluded from the published package archive (not from git).
# Tests aren't needed by consumers and keep the package small.
# Local tooling, build artifacts, and tests aren't needed by consumers.
.dart_tool/
build/
test/
3 changes: 1 addition & 2 deletions packages/insforge/lib/src/ai/images_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ class Images {
if (rawImages is List) {
for (final img in rawImages.whereType<Map<String, dynamic>>()) {
final imageUrl = img['image_url'];
final url =
imageUrl is Map<String, dynamic> ? imageUrl['url'] : null;
final url = imageUrl is Map<String, dynamic> ? imageUrl['url'] : null;
if (url is String && url.isNotEmpty) {
images.add(GeneratedImage.fromImageUrl(url));
}
Expand Down
3 changes: 1 addition & 2 deletions packages/insforge/lib/src/ai/models/chat_completion.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ class ChatCompletionResponse {
final Usage? usage;

/// Convenience accessor for the first choice's text content.
String? get content =>
choices.isEmpty ? null : choices.first.message.content;
String? get content => choices.isEmpty ? null : choices.first.message.content;

factory ChatCompletionResponse.fromJson(Map<String, dynamic> json) {
final rawChoices = json['choices'];
Expand Down
2 changes: 1 addition & 1 deletion packages/insforge/lib/src/ai/models/embeddings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class EmbeddingsRequest {
required this.input,
this.encodingFormat,
this.dimensions,
}) : assert(
}) : assert(
input is String || input is List<String>,
'input must be a String or List<String>',
);
Expand Down
1 change: 0 additions & 1 deletion packages/insforge/lib/src/auth/auth_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'dart:convert';

import 'package:insforge/insforge.dart';


/// Storage key for the persisted refresh token.
const String kRefreshTokenKey = 'insforge_refresh_token';

Expand Down
3 changes: 1 addition & 2 deletions packages/insforge/lib/src/auth/models/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ class User {
providers: rawProviders is List
? rawProviders.map((dynamic e) => e.toString()).toList()
: const <String>[],
profile:
rawProfile is Map ? Map<String, dynamic>.from(rawProfile) : null,
profile: rawProfile is Map ? Map<String, dynamic>.from(rawProfile) : null,
metadata:
rawMetadata is Map ? Map<String, dynamic>.from(rawMetadata) : null,
createdAt: parseInsforgeDate(json['createdAt'] as String?),
Expand Down
4 changes: 3 additions & 1 deletion packages/insforge/lib/src/core/http_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ class InsforgeHttpClient {
final data = response.data;
final parsed = data is Map<String, dynamic>
? ErrorResponse.fromJson(data)
: ErrorResponse(message: data?.toString() ?? 'HTTP ${response.statusCode}');
: ErrorResponse(
message: data?.toString() ?? 'HTTP ${response.statusCode}',
);
return InsforgeHttpException(
statusCode: response.statusCode ?? -1,
message: parsed.message,
Expand Down
Loading