Skip to content
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Figmage comes with a comprehensive documentation, which you can find [here](http
If this is your first time here, check out our [Getting Started Guide](https://figmage.netlify.app/guides/getting-started/)!

## What's in the box 🎁
Figmage is a magical CLI tool that helps you generate a flutter package from your Figma Design System. It uses the Figma APIs to fetch your design tokens from published **styles**, as well as **variables**, with full **modes** support.
Figmage is a magical CLI tool that helps you generate a flutter package from your Figma Design System. It uses the Figma APIs to fetch your design tokens from published **styles**, as well as **variables**, with full **modes** support. It can also read **Design Tokens JSON** exported by plugins for offline generation.

So a variables section like this:
![Example Screenshot of a Variables Section](doc/example_variables.jpg)
Expand Down Expand Up @@ -85,8 +85,9 @@ And you can use it like this:
- 🎨 Supports many types of tokens:
- 🌈 **Color** styles and variables
- 🖋️ **Typography** styles (with optional `google_fonts` support!)
- 🔢 **Number** variables, which can be generated as Paddings, and Spacers as well
- 🔢 **Number** variables, which can be generated as Paddings, and Spacers as well
- 🌓 **Modes** support for variables: Generate different tokens for different themes (e.g. dark/light)
- 📄 **JSON token sources**: Add `json.paths` in `figmage.yaml` with a resolver manifest (for example `resolver.json`) to load Design Tokens JSON across ordered sets.
- 📦 **Package** generation: All your tokens end up in one convenient package. Depend on it from your app, and update it whenever neccessary!
- 🤝 **Seamless** integration with `Theme`s from `material.dart`: Generated classes are `ThemeExtension`s, so they can be integrated into your app's theme easily!
- 🎯 **Quick access** using `BuildContext` extensions.
Expand All @@ -101,4 +102,4 @@ And you can use it like this:
[mason_link]: https://github.com/felangel/mason
[very_good_ventures_link]: https://verygood.ventures
[wnmi_badge]:
https://img.shields.io/badge/empowered_by-whynotmake.it-black?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAYAAAA8AXHiAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAe2SURBVHgB7Z2BlaM2EIbHqWBLoITtIE4HlwpMKthNBSYV5DpYXwWXVOB0cNcB6WC3g4l44Lw5kECCEUjwf%2B%2Fx%2FNaGGYG%2BNQLGQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAgTpQgzHw2L0X35%2FfT6fSdVsDkfTIvL2Y6m%2BnZTM3fH00bzPTFtONGID9Mx76Y6Z2H1GYqKCIm%2FidH7lXbAZQxHXblaS4UAc%2FcDxr5ngmkT2DHqsoVmBty5YKjY5uOezPTV44oF7ulqrgdbzXzPJvpBrkyYkSqZzFPyRHkGpGqdMxfTbUVJICPVGJeVblCpRLLQa6UCZFKLKMi11ypxPKQK0XmSCWWXSTXUqlEHMiVEkukEjFmyaUllYgHuVJAQyoRK0gubalEXJdcTwTioymViOklVyypRHybXBWBuMSQSsR2yfWp%2B%2FwlplSiHZ978b8RiEdMqUSO0pVjDam6NhSW%2FG8xch2esQ4nZUZyRZdqJP%2BDmjGg16PboNGlEvkqHueVIsDtt1U9kRtHixqYjXi2bNyoG9aRU1KQMp5SHVKunygORe%2Fvv1co1vt54vOSFOlEvdNwXW9m%2BtVMX3rvN6cg7keSSx0ejjnuFBH2L33RqopwfVNVvflwElUTth%2BRrV2kd%2BcIcvlKJeaHXJo4OnatIr2y%2BzzoDL1HviCpxHKQSwt2D6ZjF%2BmVvfm0qiJmSSWWh1xaMCdTpLe0KmKRVCIO5NKC0ynSm1sVoSKViAe5tOB0ivRCqyJUpRJxXXIVBMLgdIr0fKsiokgl4tvk%2BkwgHOZkivRc7XjtPj%2FHlEq0oy%2FXO4F5MCdTpOdqh4uKImDLw%2B0tBkAozMkU6fnKVVEEeFhuI6kZA%2FpwRjr1yu2u6E%2BOKJVHOx68UQRM3CczfZvIjaPFOXD47qikCEy040zKeEoFuZbA%2FnKVFJGRdlSkyIhUzXvN%2BOrGkEsHbm8hVPNGUol2qJxvG4k%2FJtWTmA8nUbXglcZUHu2IdRnKSyoxP%2BRaCkc%2B%2BpvRHu3LUEFSieUg11xSk0q0S%2Bsy1CypxPKQK5RUpXqwVK6lUok4kMuX1KV6MFcuLalEPMg1RS5SPeDwy1CqUom4qIpwkZtUD9j%2FMlQUqUR8m1xRf6ySPOy%2BNlZSBvB0VURUqUQ7BlURmvGzw7HRS8qIEblc94hXlUq0o%2B7lufARx1ts%2F0lYSRnC0z%2FnjypV14YxkQs6CmZlX3sb4C%2FKFG53eVNPsYgp1XUi93GOFnl4D6koN%2BlYCx6vTthSqiTkinXvBhv9Df1BK8BtbVdzkbugdWjuUfHL6XRSX79GKvNS9d5u8tzM1N8DHONeETwsBykpMvzjQLvWkovdR7drf1PVcp34iCdRNxLrPtYRM2M%2Bsb3EZ1OpxLzHkmsjsSrfDvGMt8p5ql7Oa%2Bg6HEquLcRy5J3sGEecLKQSyx5Drq3EcuT27qBu%2BaykEjH2L9eWYjnye3VUrlKJWPu%2BcL21WI42jHZY7lKJmPu9cJ2CWI52WDuO21MK2UslYtvkyv%2FCNQ8P%2FUvaCIdc3LXxrXt934tUIsc9lT5QoeusPmfakBG5XGQtVZenf1mtolxxSFVTAgTIlb1UXa5qF2K5pOKEjkg85Kr3IFWX797L9YlyY%2B2NtoQJuc4UgQ2kerHkKygn2H6RNtpG02BErpKU2UCqiyVffqcbeLgLjLbRNGGlyz8TOa6xc%2FTyXdhOQbnBw%2FM%2FJWVCTLkSkirPIkvLimR1Ii6GXAlJVVGu9NeEMkRTLkilBA8L4c6UIRpyQSpFLB2S7QXPJXJBKmXY%2FoCmKDeJXYM5ckGqSLD9sXKHkAtSRYTdjwzZtVyQagUOJtdXtn9LQ6oYHEguhlQrc1C5aki1AgeUK8plE0hl4WByqYs1IhVuF3kguWrWrYq48DQqT9DIFsgVHPvC%2FkAuhlw%2BMS8OgZrSpDvksgC5JmNdHOJUYp5XyGUBcjljXHhCKjFvCbksQK7Bshf2lEosA7lsQK7%2Fl7lwoFRiWZtcOBVxdLlY4eSnQ67PdHSOKpeGVCJWf0D%2FTuBwclXsfrpsRTPh4U1NCgKHr4pYJFWXq4ZYDvYmF%2Fs90WKxVF2uGmKNsEO5pr61KlIAYnmwJ7kmxKpICYjlyV7kcqyDqlSOPAUBO7nLxfZbCTWo%2F%2B4SYgWSq1w8XvryjZSBWDPITS7eoEgPYs0kF7l4vJw4mlwQawGpyzUiVcX2RxmryQWxFpKqXOxXpFfGkgtiKZCaXJxAkR4Pd7X5P4ViC1KQi9tLNldfqcRyLrlm%2FYTM0oaaVuZEO4Lbr%2FvmnFDR%2B%2Bh2Op1%2BowC6%2F%2FCrmULuhV443v%2FD5K9oPF9pXmz%2FBB8U9vzsJxo%2Bfzt4%2FUEPjW%2BukRhzqALylqxPzRhf6cAL5GI9qZoxTvCTH7iVy6cCwoea9%2F4U%2B7XhGXKxjlQ1t6cTZg%2BWu3bceL5g70vbsJRdjbH68MiYy0y%2Fm3HHh8e8X8xUkQcm3r%2BkTCdHiCAfcr1AJNj9LdS899b9Z98d%2F%2Fk3AsAFz9vF3QiAKQLlws%2BlQBjdrs8lWHOjjfye4Zcgux68j8Ht0zKaQ%2FFmYNwMdr%2BbQe8%2FBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASfAfQWaAgS7EyI8AAAAASUVORK5CYII%3D
https://img.shields.io/badge/empowered_by-whynotmake.it-black?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAYAAAA8AXHiAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAe2SURBVHgB7Z2BlaM2EIbHqWBLoITtIE4HlwpMKthNBSYV5DpYXwWXVOB0cNcB6WC3g4l44Lw5kECCEUjwf%2B%2Fx%2FNaGGYG%2BNQLGQAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAgTpQgzHw2L0X35%2FfT6fSdVsDkfTIvL2Y6m%2BnZTM3fH00bzPTFtONGID9Mx76Y6Z2H1GYqKCIm%2FidH7lXbAZQxHXblaS4UAc%2FcDxr5ngmkT2DHqsoVmBty5YKjY5uOezPTV44oF7ulqrgdbzXzPJvpBrkyYkSqZzFPyRHkGpGqdMxfTbUVJICPVGJeVblCpRLLQa6UCZFKLKMi11ypxPKQK0XmSCWWXSTXUqlEHMiVEkukEjFmyaUllYgHuVJAQyoRK0gubalEXJdcTwTioymViOklVyypRHybXBWBuMSQSsR2yfWp%2B%2FwlplSiHZ978b8RiEdMqUSO0pVjDam6NhSW%2FG8xch2esQ4nZUZyRZdqJP%2BDmjGg16PboNGlEvkqHueVIsDtt1U9kRtHixqYjXi2bNyoG9aRU1KQMp5SHVKunygORe%2Fvv1co1vt54vOSFOlEvdNwXW9m%2BtVMX3rvN6cg7keSSx0ejjnuFBH2L33RqopwfVNVvflwElUTth%2BRrV2kd%2BcIcvlKJeaHXJo4OnatIr2y%2BzzoDL1HviCpxHKQSwt2D6ZjF%2BmVvfm0qiJmSSWWh1xaMCdTpLe0KmKRVCIO5NKC0ynSm1sVoSKViAe5tOB0ivRCqyJUpRJxXXIVBMLgdIr0fKsiokgl4tvk%2BkwgHOZkivRc7XjtPj%2FHlEq0oy%2FXO4F5MCdTpOdqh4uKImDLw%2B0tBkAozMkU6fnKVVEEeFhuI6kZA%2FpwRjr1yu2u6E%2BOKJVHOx68UQRM3CczfZvIjaPFOXD47qikCEy040zKeEoFuZbA%2FnKVFJGRdlSkyIhUzXvN%2BOrGkEsHbm8hVPNGUol2qJxvG4k%2FJtWTmA8nUbXglcZUHu2IdRnKSyoxP%2BRaCkc%2B%2BpvRHu3LUEFSieUg11xSk0q0S%2Bsy1CypxPKQK5RUpXqwVK6lUok4kMuX1KV6MFcuLalEPMg1RS5SPeDwy1CqUom4qIpwkZtUD9j%2FMlQUqUR8m1xRf6ySPOy%2BNlZSBvB0VURUqUQ7BlURmvGzw7HRS8qIEblc94hXlUq0o%2B7lufARx1ts%2F0lYSRnC0z%2FnjypV14YxkQs6CmZlX3sb4C%2FKFG53eVNPsYgp1XUi93GOFnl4D6koN%2BlYCx6vTthSqiTkinXvBhv9Df1BK8BtbVdzkbugdWjuUfHL6XRSX79GKvNS9d5u8tzM1N8DHONeETwsBykpMvzjQLvWkovdR7drf1PVcp34iCdRNxLrPtYRM2M%2Bsb3EZ1OpxLzHkmsjsSrfDvGMt8p5ql7Oa%2Bg6HEquLcRy5J3sGEecLKQSyx5Drq3EcuT27qBu%2BaykEjH2L9eWYjnye3VUrlKJWPu%2BcL21WI42jHZY7lKJmPu9cJ2CWI52WDuO21MK2UslYtvkyv%2FCNQ8P%2FUvaCIdc3LXxrXt934tUIsc9lT5QoeusPmfakBG5XGQtVZenf1mtolxxSFVTAgTIlb1UXa5qF2K5pOKEjkg85Kr3IFWX797L9YlyY%2B2NtoQJuc4UgQ2kerHkKygn2H6RNtpG02BErpKU2UCqiyVffqcbeLgLjLbRNGGlyz8TOa6xc%2FTyXdhOQbnBw%2FM%2FJWVCTLkSkirPIkvLimR1Ii6GXAlJVVGu9NeEMkRTLkilBA8L4c6UIRpyQSpFLB2S7QXPJXJBKmXY%2FoCmKDeJXYM5ckGqSLD9sXKHkAtSRYTdjwzZtVyQagUOJtdXtn9LQ6oYHEguhlQrc1C5aki1AgeUK8plE0hl4WByqYs1IhVuF3kguWrWrYq48DQqT9DIFsgVHPvC%2FkAuhlw%2BMS8OgZrSpDvksgC5JmNdHOJUYp5XyGUBcjljXHhCKjFvCbksQK7Bshf2lEosA7lsQK7%2Fl7lwoFRiWZtcOBVxdLlY4eSnQ67PdHSOKpeGVCJWf0D%2FTuBwclXsfrpsRTPh4U1NCgKHr4pYJFWXq4ZYDvYmF%2Fs90WKxVF2uGmKNsEO5pr61KlIAYnmwJ7kmxKpICYjlyV7kcqyDqlSOPAUBO7nLxfZbCTWo%2F%2B4SYgWSq1w8XvryjZSBWDPITS7eoEgPYs0kF7l4vJw4mlwQawGpyzUiVcX2RxmryQWxFpKqXOxXpFfGkgtiKZCaXJxAkR4Pd7X5P4ViC1KQi9tLNldfqcRyLrlm%2FYTM0oaaVuZEO4Lbr%2FvmnFDR%2B%2Bh2Op1%2BowC6%2F%2FCrmULuhV443v%2FD5K9oPF9pXmz%2FBB8U9vzsJxo%2Bfzt4%2FUEPjW%2BukRhzqALylqxPzRhf6cAL5GI9qZoxTvCTH7iVy6cCwoea9%2F4U%2B7XhGXKxjlQ1t6cTZg%2BWu3bceL5g70vbsJRdjbH68MiYy0y%2Fm3HHh8e8X8xUkQcm3r%2BkTCdHiCAfcr1AJNj9LdS899b9Z98d%2F%2Fk3AsAFz9vF3QiAKQLlws%2BlQBjdrs8lWHOjjfye4Zcgux68j8Ht0zKaQ%2FFmYNwMdr%2BbQe8%2FBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASfAfQWaAgS7EyI8AAAAASUVORK5CYII%3D
95 changes: 95 additions & 0 deletions doc/json_token_source_spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
JSON Token Source Spec
Status: implemented

Overview
Figmage supports JSON token ingestion via a resolver-manifest entrypoint.
JSON source data is merged with Figma API tokens (if configured) and uses the
same downstream generation pipeline.

Resolver Entrypoint
`json.paths` must contain exactly one file path, and that path must point to a
resolver manifest JSON file.

Resolver format (required fields):
- `version`: non-empty string (currently minimally validated)
- `sets`: object where each key is a set name and each value is an object with
`"$ref": "./some-file.json"`
- `resolutionOrder`: non-empty list of set names

Example:
```json
{
"version": "2025.10",
"sets": {
"global": { "$ref": "./global.json" },
"Color Primitives": { "$ref": "./Color Primitives.json" },
"Color Aliases": { "$ref": "./Color Aliases.json" }
},
"resolutionOrder": ["global", "Color Primitives", "Color Aliases"]
}
```

Validation
Figmage throws when:
- `json.paths` does not contain exactly one path
- resolver file is missing or invalid JSON
- resolver shape is invalid (`version`/`sets`/`resolutionOrder`)
- `resolutionOrder` references a set not present in `sets`
- resolver set file path is missing/unreadable
- the resolver references the same resolved set file path more than once

Token Parsing
- Token objects must include `$type` and `$value`.
- Supported `$type` values:
- `color` -> `int`
- `number` -> `double`
- `boolean` -> `bool`
- `text` -> `String`
- `typography` -> `Typography`
- Mode inference remains path-based (same behavior as previous JSON mode):
second segment can become mode when branch signatures match.

Reference Resolution
Figmage uses a two-pass pipeline:
1. Parse all resolver-ordered set files and collect token definitions.
2. Resolve aliases/refs and emit `AliasOr` structures.

Supported references:
- Curly alias: `"{Color Primitives.Value.stone.50}"`
- Resolves against the resolver-expanded virtual token graph across all files
in `resolutionOrder`.
- Dot-separated segments map to JSON key path segments.
- If no match exists, value becomes unresolved and a diagnostic is emitted.
- If multiple matches exist, value becomes unresolved and an ambiguity
diagnostic is emitted.
- Cross-file `$ref` inside `$value`:
- `"$value": { "$ref": "./Color Primitives.json#/Value/stone/50" }`
- Relative file paths resolve from the resolver manifest directory.
- Empty file part (for example `#/Color Primitives/Value/stone/50`) resolves
in the current file.
- JSON Pointer syntax is supported and validated.
- Pointer targets are validated by usage context:
- direct token object pointers resolve
- token `$value` pointers resolve to the owning token alias
- non-token targets become unresolved with diagnostics

Failure Behavior
Reference failures do not abort JSON ingestion. Instead, Figmage:
- emits diagnostics through repository diagnostics callback
- marks the value as unresolved (`AliasOr.unresolved`)

This includes:
- missing target
- invalid pointer
- target not a token object
- type mismatch
- cyclic references
- malformed alias/ref syntax

Interaction with `dropUnresolved`
- `dropUnresolved: true` removes tokens that have unresolved values in any mode.
- `dropUnresolved: false` keeps tokens; unresolved values resolve to `null`.

Non-Goals (current scope)
- Resolver `modifiers` / runtime context selection
- Full Resolver-module execution semantics beyond ordered set expansion
182 changes: 182 additions & 0 deletions integration_test/json_path_integration_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import 'dart:io';

import 'package:figmage/src/domain/models/config/config.dart';
import 'package:figmage/src/domain/providers/figmage_package_generator_providers.dart';
import 'package:figmage/src/domain/providers/generator_providers.dart';
import 'package:figmage/src/domain/providers/library_provider.dart';
import 'package:path/path.dart' as p;
import 'package:riverpod/riverpod.dart';
import 'package:test/test.dart';

void main() {
group('json resolver integration', () {
test(
'generates file-scoped classes and keeps resolver aliases resolved',
() async {
final fakeGeneratedFiles = <File>[
File('${Directory.systemTemp.path}/colors.dart'),
File('${Directory.systemTemp.path}/typography.dart'),
];

final container = ProviderContainer(
overrides: [
generatedPackageProvider.overrideWith((ref, settings) async {
return fakeGeneratedFiles;
}),
],
);
addTearDown(container.dispose);

final settings = (
fileId: null,
token: null,
path: Directory.current.path,
config: const Config(
dropUnresolved: true,
colors: GenerationSettings(from: ['Color Aliases']),
json: JsonTokenSourceConfig(
paths: [r'test/fixtures/json/sample_system/$resolver.json'],
),
),
);

final generators = await container.read(
generatorsProvider(settings).future,
);
final codeByFile = container.read(librariesProvider(generators));

final colorsCode = codeByFile.entries
.singleWhere((entry) => p.basename(entry.key.path) == 'colors.dart')
.value;
final typographyCode = codeByFile.entries
.singleWhere(
(entry) => p.basename(entry.key.path) == 'typography.dart',
)
.value;

expect(colorsCode, contains('class ColorAliases'));
expect(colorsCode, contains('final Color paletteCorePrimary;'));
expect(colorsCode, contains('final Color paletteCoreSystemLink;'));
expect(
colorsCode,
contains('paletteCorePrimary = const Color(0xfff14f28),'),
);
expect(
colorsCode,
contains('paletteCoreSystemLink = const Color(0xff2f9bff),'),
);
expect(colorsCode, contains('final Color paletteCoreOverlay5;'));
expect(
colorsCode,
contains('paletteCoreOverlay5 = const Color(0x0d5d8fa9),'),
);

expect(typographyCode, contains('class Display'));
expect(typographyCode, contains('class Headline'));
expect(typographyCode, contains('class Body'));
expect(typographyCode, contains('class Label'));
expect(typographyCode, contains('final TextStyle display1;'));
expect(typographyCode, contains('final TextStyle body1;'));
expect(
typographyCode,
contains('fontWeight: const FontWeight(463),'),
);
},
);

test(
'does not collapse branches with different schemas '
'into one nullable class',
() async {
final tempDir = await Directory.systemTemp.createTemp(
'figmage_json_branching_',
);
addTearDown(() async {
if (tempDir.existsSync()) {
await tempDir.delete(recursive: true);
}
});

final resolver = File('${tempDir.path}/\$resolver.json');
final theme = File('${tempDir.path}/theme.json');

await theme.writeAsString(
'''
{
"colors": {
"light": {
"surface": {"\$type": "color", "\$value": "#ffffff"},
"text": {"\$type": "color", "\$value": "#111111"}
},
"dark": {
"surface": {"\$type": "color", "\$value": "#000000"},
"text": {"\$type": "color", "\$value": "#f0f0f0"}
},
"compact": {
"badge": {"\$type": "color", "\$value": "#ff0000"}
}
}
}
''',
);

await resolver.writeAsString(
'''
{
"version": "2025.10",
"sets": {
"theme": {"\$ref": "./theme.json"}
},
"resolutionOrder": ["theme"]
}
''',
);

final fakeGeneratedFiles = <File>[
File('${Directory.systemTemp.path}/colors.dart'),
];

final container = ProviderContainer(
overrides: [
generatedPackageProvider.overrideWith((ref, settings) async {
return fakeGeneratedFiles;
}),
],
);
addTearDown(container.dispose);

final settings = (
fileId: null,
token: null,
path: tempDir.path,
config: Config(
dropUnresolved: true,
colors: const GenerationSettings(from: ['theme']),
json: const JsonTokenSourceConfig(paths: [r'$resolver.json']),
),
);

final generators = await container.read(
generatorsProvider(settings).future,
);
final codeByFile = container.read(librariesProvider(generators));
final colorsCode = codeByFile.entries
.singleWhere((entry) => p.basename(entry.key.path) == 'colors.dart')
.value;

final classCount = RegExp(
r'class\s+\w+\s+extends\s+ThemeExtension<',
).allMatches(colorsCode).length;

// Mixed schemas are emitted as one class with branch-prefixed fields.
expect(classCount, 1);
expect(colorsCode, isNot(contains('final Color?')));
expect(colorsCode, contains('final Color lightSurface;'));
expect(colorsCode, contains('final Color darkSurface;'));
expect(colorsCode, contains('final Color compactBadge;'));
expect(colorsCode, isNot(contains('.light(')));
expect(colorsCode, isNot(contains('.dark(')));
},
);
});
}
3 changes: 1 addition & 2 deletions lib/src/commands/forge/forge_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ class ForgeCommand extends Command<int> {
..addOption(
"token",
abbr: "t",
help: "Your figma API token",
mandatory: true,
help: "Your figma API token (required when using fileId)",
)
..addOption(
"path",
Expand Down
32 changes: 24 additions & 8 deletions lib/src/commands/shared/forge_settings_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,31 @@ final settingsProvider = FutureProvider.autoDispose

final config = await ref.watch(configProvider(configPath).future);

final fileIdFromArgs = args.wasParsed('fileId') ? args['fileId'] : null;
final fileId = switch (fileIdFromArgs ?? config.fileId) {
final String fileId => fileId,
_ => null,
};

final tokenFromArgs = args.wasParsed('token') ? args['token'] : null;
final token = switch (tokenFromArgs) {
final String token => token,
_ => null,
};

final hasJsonSources = config.json.paths.isNotEmpty;

if (fileId == null && hasJsonSources == false) {
throw ArgumentError.notNull('fileId');
}

if (fileId != null && token == null) {
throw ArgumentError.notNull('token');
}

return (
token: switch (args['token']) {
final String token => token,
_ => throw ArgumentError.notNull('token'),
},
fileId: switch (args['fileId'] ?? config.fileId) {
final String fileId => fileId,
_ => throw ArgumentError.notNull('fileId'),
},
token: token,
fileId: fileId,
path: dir.path,
config: config,
);
Expand Down
7 changes: 4 additions & 3 deletions lib/src/commands/shared/generation_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ class GenerationNotifier
filteredTokensProvider(settings).future,
);
} catch (e) {
logger.err("Neither styles nor variables could be obtained from file "
"${settings.fileId}");
if (settings.config.stylesFromLibrary) {
logger.err(
"No tokens could be obtained from the configured sources.",
);
if (settings.config.stylesFromLibrary && settings.fileId != null) {
logger.warn(
"Make sure you have published the file's library, "
"or set stylesFromLibrary to false.",
Expand Down
Loading