NEW FLAVOUR: COMMUNITY

This commit is contained in:
Mayel de Borniol 2023-03-02 18:56:54 +13:00
parent 29516318ad
commit 42c5558865
42 changed files with 700 additions and 12 deletions

View file

@ -4,8 +4,8 @@
# NOTE: any LV Hooks should also be added to ./deps_hooks.js
# TODO: make this more configurable? ie. autogenerate from active extensions with JS assets
DEPS='iconify_ex bonfire_ui_common bonfire_editor_quill bonfire_editor_ck'
#
DEPS='iconify_ex bonfire_ui_common'
# bonfire_editor_quill bonfire_editor_ck
chmod +x ./js-deps-get.sh
./js-deps-get.sh "$DEPS" $@

View file

@ -20,14 +20,18 @@ config :bonfire, Bonfire.Common.Repo,
stacktrace: true
local_deps =
Mess.deps(
if(System.get_env("WITH_FORKS", "1") == "1",
do: [path: Path.relative_to_cwd("config/deps.path")],
else: []
),
[]
Mess.read_umbrella(
config_dir: "config/",
use_local_forks?: System.get_env("WITH_FORKS", "1") == "1"
)
# if System.get_env("WITH_FORKS", "1") == "1" , do:
# Mess.deps(
# [path: Path.relative_to_cwd("config/deps.path")],
# []
# ),
# else: []
local_dep_names = Enum.map(local_deps, &elem(&1, 0))
dep_paths =

View file

@ -0,0 +1,19 @@
# Bonfire Community
This app is a flavour of [Bonfire](https://bonfirenetworks.org/) and bundles the following extensions:
- [Bonfire.Common](https://github.com/bonfire-networks/bonfire_common) - common utils
- [Bonfire.Me](https://github.com/bonfire-networks/bonfire_me) - accounts, user profiles...
- [Bonfire.Social](https://github.com/bonfire-networks/bonfire_social) - feeds, activities, posts, boosting, flagging, etc...
- [Bonfire.UI.Social](https://github.com/bonfire-ecosystem/bonfire_ui_social) - interface for basic social activities
- [Bonfire.Boundaries](https://github.com/bonfire-networks/bonfire_boundaries) - define circles and associated privacy or permissions
- [Bonfire.Federate.ActivityPub](https://github.com/bonfire-networks/bonfire_federate_activitypub) - federates activities with ActivityPub to participate in the fediverse
- [Bonfire.Tag](https://github.com/bonfire-ecosystem/bonfire_tag) - @ mentions, hashtags, and tagging using topics/categories from Bonfire.Classify
- [Bonfire.Classify](https://github.com/bonfire-ecosystem/bonfire_classify) - categories & classifications in taxonomies
- [Bonfire.UI.Groups](https://github.com/bonfire-ecosystem/bonfire_ui_groups) - groups
- [Bonfire.UI.Topics](https://github.com/bonfire-ecosystem/bonfire_ui_topics) - topics
## More information
See the [main docs](../../README.md).

View file

@ -0,0 +1,5 @@
## Flavour:COOPERATION
bonfire_api_graphql = "https://github.com/bonfire-networks/bonfire_api_graphql#main"
bonfire_ui_topics = "https://github.com/bonfire-networks/bonfire_ui_topics#main"
bonfire_ui_groups = "https://github.com/bonfire-networks/bonfire_ui_groups#main"
bonfire_classify = "https://github.com/bonfire-networks/bonfire_classify#main"

View file

@ -0,0 +1,3 @@
# API
#absinthe = "~> 1.7.0"

View file

@ -0,0 +1,4 @@
bonfire_api_graphql = "extensions/bonfire_api_graphql"
bonfire_classify = "extensions/bonfire_classify"
bonfire_ui_topics = "extensions/bonfire_ui_topics"
bonfire_ui_groups = "extensions/bonfire_ui_groups"

View file

@ -0,0 +1,11 @@
#!/bin/sh
# Add any extensions/deps with a package.json in their /assets directory here
# NOTE: any LV Hooks should also be added to ./deps_hooks.js
# TODO: make this more configurable? ie. autogenerate from active extensions with JS assets
DEPS='iconify_ex bonfire_ui_common'
# bonfire_editor_quill bonfire_editor_ck
chmod +x ./js-deps-get.sh
./js-deps-get.sh "$DEPS" $@

View file

@ -0,0 +1,33 @@
let ExtensionHooks = {};
// NOTE: any extensions included here need to also be added to ./deps.js.sh
// NOTE: during development you may want to change 'deps' in the path to 'forks', but remember to change it back before committing!
// TODO: make this more configurable? ie. don't import disabled extensions
import { ChangeLocaleHooks } from "./../../../deps/bonfire_ui_common/assets/js/change_locale"
import { InputSelectHooks } from "./../../../deps/bonfire_ui_common/assets/js/input_select"
import { NotificationsHooks } from "./../../../deps/bonfire_ui_common/assets/js/notifications"
import { CarouselHooks } from "./../../../deps/bonfire_ui_common/assets/js/carousel"
import { ResponsiveTabsHooks } from "./../../../deps/bonfire_ui_common/assets/js/responsive_tabs"
import { ThemeHooks } from "./../../../deps/bonfire_ui_common/assets/js/theme"
import { PopupHooks } from "./../../../deps/bonfire_ui_common/assets/js/popup"
import { FeedHooks } from "./../../../deps/bonfire_ui_common/assets/js/feed"
import { ImageHooks } from "./../../../deps/bonfire_ui_common/assets/js/image"
import { EmojiHooks } from "./../../../deps/bonfire_ui_common/assets/js/emoji"
// import { EditorCkHooks } from "./../../../deps/bonfire_editor_ck/assets/js/extension"
// import { EditorQuillHooks } from "./../../../deps/bonfire_editor_quill/assets/js/extension"
import { ComposerHooks } from "./../../../deps/bonfire_ui_common/assets/js/composer"
import { CodeHooks } from "./../../../deps/bonfire_ui_common/assets/js/code"
// import { GeolocateHooks } from "./../../../deps/bonfire_geolocate/assets/js/extension"
// import { KanbanHooks } from "./../../../deps/bonfire_ui_kanban/assets/js/extension"
// import { EncryptHooks } from "./../../../deps/bonfire_encrypt/assets/js/extension"
// import LiveSelect from "./../../../deps/live_select/assets/js/live_select"
import LiveSelect from "./../../../deps/live_select/priv/static/live_select.min.js"
Object.assign(ExtensionHooks, ComposerHooks, PopupHooks, EmojiHooks, ResponsiveTabsHooks, CarouselHooks, FeedHooks, ChangeLocaleHooks, ImageHooks, InputSelectHooks, NotificationsHooks, ThemeHooks, LiveSelect, CodeHooks) // ImageHooks, EditorCkHooks, EditorQuillHooks
export { ExtensionHooks }

View file

@ -0,0 +1,14 @@
import Config
config :bonfire_api_graphql,
disabled: false
# Please note that most of these are defaults meant to be overridden by instance admins in Settings rather than edited here
config :bonfire, :ui,
# end theme
hide_app_switcher: false,
# feed_object_extension_preloads_disabled: false,
smart_input_activities: [
category: "Create a topic",
label: "New label"
]

View file

@ -0,0 +1,4 @@
[
import_deps: [:ecto_sql],
inputs: ["*.exs"]
]

View file

@ -0,0 +1,12 @@
defmodule Bonfire.Repo.Migrations.HelloWorld do
@moduledoc false
use Ecto.Migration
def up do
execute("CREATE EXTENSION IF NOT EXISTS \"citext\"")
end
def down do
execute("DROP EXTENSION IF EXISTS \"citext\"")
end
end

View file

@ -0,0 +1,15 @@
defmodule Bonfire.Repo.Migrations.InitPointers do
@moduledoc false
use Ecto.Migration
import Pointers.Migration
def up do
init_pointers_ulid_extra()
init_pointers()
end
def down do
init_pointers_ulid_extra()
init_pointers()
end
end

View file

@ -0,0 +1,13 @@
defmodule Bonfire.Repo.Migrations.InitEdges do
@moduledoc false
use Ecto.Migration
alias Bonfire.Data.Edges.Migration
def up do
Migration.up()
end
def down do
Migration.down()
end
end

View file

@ -0,0 +1,12 @@
defmodule Bonfire.Repo.Migrations.CreateApTables do
@moduledoc false
use Ecto.Migration
def up do
ActivityPub.Migrations.up()
end
def down do
ActivityPub.Migrations.down()
end
end

View file

@ -0,0 +1,12 @@
defmodule Bonfire.Repo.Migrations.CreateObanTables do
@moduledoc false
use Ecto.Migration
def up do
Oban.Migrations.up()
end
def down do
Oban.Migrations.down(version: 1)
end
end

View file

@ -0,0 +1,10 @@
defmodule Bonfire.Federate.ActivityPub.Repo.Migrations.ImportMe do
@moduledoc false
use Ecto.Migration
import Bonfire.Federate.ActivityPub.Migrations
# accounts & users
def up, do: migrate_activity_pub
def down, do: migrate_activity_pub
end

View file

@ -0,0 +1,9 @@
defmodule Bonfire.Boundaries.Repo.Migrations.ImportBoundaries do
@moduledoc false
use Ecto.Migration
import Bonfire.Boundaries.Migrations
def up, do: migrate_boundaries()
def down, do: migrate_boundaries()
end

View file

@ -0,0 +1,15 @@
defmodule Bonfire.Repo.Migrations.AddFiles do
@moduledoc false
use Ecto.Migration
import Bonfire.Files.Media.Migrations
import Pointers.Migration
def up do
Bonfire.Files.Media.Migrations.migrate_media()
end
def down do
Bonfire.Files.Media.Migrations.migrate_media()
end
end

View file

@ -0,0 +1,13 @@
defmodule Bonfire.Repo.Migrations.ImportMe do
@moduledoc false
use Ecto.Migration
import Bonfire.Me.Migrations
def up do
# accounts & users
migrate_me()
end
def down, do: migrate_me()
end

View file

@ -0,0 +1,13 @@
defmodule Bonfire.Social.Repo.Migrations.ImportSocial do
@moduledoc false
use Ecto.Migration
import Bonfire.Social.Migrations
import Pointers.Migration
def up do
migrate_social()
end
def down, do: migrate_social()
end

View file

@ -0,0 +1,9 @@
defmodule Bonfire.Boundaries.Repo.Migrations.BoundariesFixtures do
@moduledoc false
use Ecto.Migration
import Bonfire.Boundaries.Fixtures
def up, do: Bonfire.Boundaries.Fixtures.insert()
def down, do: nil
end

View file

@ -0,0 +1,12 @@
defmodule Bonfire.Repo.Migrations.Tag do
@moduledoc false
use Ecto.Migration
def up do
Bonfire.Tag.Migrations.up()
end
def down do
Bonfire.Tag.Migrations.down()
end
end

View file

@ -0,0 +1,10 @@
defmodule Bonfire.Repo.Migrations.ImportSharedUser do
@moduledoc false
use Ecto.Migration
import Bonfire.Data.SharedUser.Migration
# accounts & users
def up, do: migrate_shared_user()
def down, do: migrate_shared_user()
end

View file

@ -0,0 +1,16 @@
defmodule ActivityPub.Repo.Migrations.APTestTable do
@moduledoc false
use Ecto.Migration
def change do
# This table only exists for test purposes
create table("local_actor", primary_key: false) do
add(:id, :uuid, primary_key: true)
add(:username, :citext)
add(:data, :map)
add(:local, :boolean)
add(:keys, :text)
add(:followers, {:array, :string})
end
end
end

View file

@ -0,0 +1,23 @@
defmodule Bonfire.Social.Repo.Migrations.ProfileImages do
@moduledoc false
use Ecto.Migration
import Pointers.Migration
def up do
drop_if_exists(
constraint("bonfire_data_social_profile", "bonfire_data_social_profile_icon_id_fkey")
)
drop_if_exists(
constraint("bonfire_data_social_profile", "bonfire_data_social_profile_image_id_fkey")
)
alter table("bonfire_data_social_profile") do
Ecto.Migration.add_if_not_exists(:icon_id, strong_pointer(Bonfire.Files.Media))
Ecto.Migration.add_if_not_exists(:image_id, strong_pointer(Bonfire.Files.Media))
end
end
def down, do: nil
end

View file

@ -0,0 +1,42 @@
defmodule Bonfire.Repo.Migrations.FP do
@moduledoc false
use Ecto.Migration
def up do
execute("create or replace function
column_exists(ptable text, pcolumn text, pschema text default 'public')
returns boolean
language sql stable strict
as $body$
-- does the requested table.column exist in schema?
select exists
( select null
from information_schema.columns
where table_name=ptable
and column_name=pcolumn
and table_schema=pschema
);
$body$;")
execute(
"CREATE OR REPLACE FUNCTION rename_column_if_exists(ptable TEXT, pcolumn TEXT, new_name TEXT)
RETURNS VOID AS $BODY$
BEGIN
-- Rename the column if it exists.
IF column_exists(ptable, pcolumn) THEN
EXECUTE FORMAT('ALTER TABLE IF EXISTS %I RENAME COLUMN %I TO %I;',
ptable, pcolumn, new_name);
END IF;
END$BODY$
LANGUAGE plpgsql VOLATILE;"
)
flush()
execute(
"SELECT rename_column_if_exists('bonfire_data_social_feed_publish', 'object_id', 'activity_id') "
)
end
def down, do: nil
end

View file

@ -0,0 +1,14 @@
defmodule Bonfire.Social.Repo.Migrations.PeeredURI do
@moduledoc false
use Ecto.Migration
import Pointers.Migration
def up do
alter table("bonfire_data_activity_pub_peered") do
Ecto.Migration.add_if_not_exists(:canonical_uri, :text, null: true)
end
end
def down, do: nil
end

View file

@ -0,0 +1,8 @@
defmodule Bonfire.Repo.Migrations.UpdateObanJobsTable do
@moduledoc false
use Ecto.Migration
def up, do: Oban.Migrations.up()
def down, do: nil
end

View file

@ -0,0 +1,8 @@
defmodule Bonfire.Repo.Migrations.ImportApActivity do
@moduledoc false
use Ecto.Migration
import Bonfire.Data.Social.APActivity.Migration
def up(), do: migrate_apactivity()
def down(), do: migrate_apactivity()
end

View file

@ -0,0 +1,12 @@
defmodule Bonfire.Repo.Migrations.ImportClassify do
@moduledoc false
use Ecto.Migration
def up do
Bonfire.Classify.Migrations.up()
end
def down do
Bonfire.Classify.Migrations.down()
end
end

View file

@ -0,0 +1,12 @@
defmodule Bonfire.Repo.Migrations.ImportInviteLink do
@moduledoc false
use Ecto.Migration
def up do
Bonfire.Invites.Link.Migration.up()
end
def down do
Bonfire.Invites.Link.Migration.down()
end
end

View file

@ -0,0 +1,8 @@
defmodule Bonfire.Data.Identity.Repo.Migrations.CareClosure do
@moduledoc false
use Ecto.Migration
alias Bonfire.Data.Identity.CareClosure.Migration
def change, do: Migration.migrate_care_closure_view()
end

View file

@ -0,0 +1,13 @@
defmodule Bonfire.Repo.Migrations.ImportSettings do
@moduledoc false
use Ecto.Migration
require Bonfire.Data.Identity.Settings.Migration
def up do
Bonfire.Data.Identity.Settings.Migration.migrate_settings(:up)
end
def down do
Bonfire.Data.Identity.Settings.Migration.migrate_settings(:down)
end
end

View file

@ -0,0 +1,21 @@
defmodule Bonfire.Repo.Migrations.AddFilesMixin do
@moduledoc false
use Ecto.Migration
import Bonfire.Files.Migrations
import Pointers.Migration
def up do
# cleanup old stuff
alter table("bonfire_files_media") do
remove_if_exists(:created_at, :utc_datetime_usec)
remove_if_exists(:updated_at, :utc_datetime_usec)
end
Bonfire.Files.Migrations.migrate_files()
end
def down do
Bonfire.Files.Migrations.migrate_files()
end
end

View file

@ -0,0 +1,13 @@
defmodule Bonfire.Repo.Migrations.Hashtag do
@moduledoc false
use Ecto.Migration
require Bonfire.Tag.Hashtag.Migration
def up do
Bonfire.Tag.Hashtag.Migration.migrate_hashtag()
end
def down do
Bonfire.Tag.Hashtag.Migration.migrate_hashtag()
end
end

View file

@ -0,0 +1,13 @@
defmodule Bonfire.Repo.Migrations.Seen do
@moduledoc false
use Ecto.Migration
require Bonfire.Data.Social.Seen.Migration
def up do
Bonfire.Data.Social.Seen.Migration.migrate_seen()
end
def down do
Bonfire.Data.Social.Seen.Migration.migrate_seen()
end
end

View file

@ -0,0 +1,13 @@
defmodule Bonfire.Repo.Migrations.AuthSecondFactor do
@moduledoc false
use Ecto.Migration
require Bonfire.Data.Identity.AuthSecondFactor.Migration
def up do
Bonfire.Data.Identity.AuthSecondFactor.Migration.migrate_auth_second_factor()
end
def down do
Bonfire.Data.Identity.AuthSecondFactor.Migration.migrate_auth_second_factor()
end
end

View file

@ -0,0 +1,13 @@
defmodule Bonfire.Repo.Migrations.ExtraInfo do
@moduledoc false
use Ecto.Migration
require Bonfire.Data.Identity.ExtraInfo.Migration
def up do
Bonfire.Data.Identity.ExtraInfo.Migration.migrate_extra_info(:up)
end
def down do
Bonfire.Data.Identity.ExtraInfo.Migration.migrate_extra_info(:down)
end
end

View file

@ -0,0 +1,9 @@
defmodule Bonfire.Boundaries.Repo.Migrations.BoundariesFixturesUp do
@moduledoc false
use Ecto.Migration
import Bonfire.Boundaries.Fixtures
def up, do: Bonfire.Boundaries.Fixtures.insert()
def down, do: nil
end

View file

@ -0,0 +1,12 @@
defmodule ActivityPub.Repo.Migrations.AddObjectBoolean do
@moduledoc false
use Ecto.Migration
def up do
ActivityPub.Migrations.add_object_boolean()
end
def down do
ActivityPub.Migrations.drop_object_boolean()
end
end

View file

@ -0,0 +1,185 @@
# Script for populating the database. You can run it with `mix ecto.seeds`
#
import Bonfire.Me.Fake
System.put_env("INVITE_ONLY", "false")
System.put_env("SEARCH_INDEXING_DISABLED", "true")
# if the user has configured an admin user for the seeds, insert it.
case {System.get_env("ADMIN_USER", "root"), System.get_env("ADMIN_PASSWORD", "")} do
{u, p} when p != "" ->
fake_account!(%{credential: %{password: p}})
|> fake_user!(%{character: %{username: u}, profile: %{name: u}})
|> Bonfire.Me.Users.make_admin()
_ ->
nil
end
# create some users
users = for _ <- 1..3, do: fake_user!()
random_user = fn -> Faker.Util.pick(users) end
# start fake threads
# for _ <- 1..3 do
# user = random_user.()
# thread = fake_thread!(user)
# comment = fake_comment!(user, thread)
# # reply to it
# reply = fake_comment!(random_user.(), thread, %{in_reply_to_id: comment.id})
# subreply = fake_comment!(random_user.(), thread, %{in_reply_to_id: reply.id})
# subreply2 = fake_comment!(random_user.(), thread, %{in_reply_to_id: subreply.id})
# end
#
## more fake threads
# for _ <- 1..2 do
# user = random_user.()
# thread = fake_thread!(user)
# comment = fake_comment!(user, thread)
# end
# define some tags/categories
if(Bonfire.Common.Extend.extension_enabled?(Bonfire.Classify.Simulate)) do
for _ <- 1..2 do
category = Bonfire.Classify.Simulate.fake_category!(random_user.())
_subcategory = Bonfire.Classify.Simulate.fake_category!(random_user.(), category)
end
end
# define some geolocations
if(Bonfire.Common.Extend.extension_enabled?(Bonfire.Geolocate.Simulate)) do
for _ <- 1..2,
do: Bonfire.Geolocate.Simulate.fake_geolocation!(random_user.())
for _ <- 1..2,
do: Bonfire.Geolocate.Simulate.fake_geolocation!(random_user.())
end
# define some units
if(Bonfire.Common.Extend.extension_enabled?(Bonfire.Quantify.Simulate)) do
for _ <- 1..2 do
_unit1 = Bonfire.Quantify.Simulate.fake_unit!(random_user.())
_unit2 = Bonfire.Quantify.Simulate.fake_unit!(random_user.())
end
end
# conduct some fake economic activities
if(Bonfire.Common.Extend.extension_enabled?(ValueFlows.Simulate)) do
for _ <- 1..2 do
user = random_user.()
action_id = ValueFlows.Simulate.action_id()
# some lonesome intents and proposals
_intent = ValueFlows.Simulate.fake_intent!(user, %{action_id: action_id})
_proposal = ValueFlows.Simulate.fake_proposal!(user)
end
for _ <- 1..2 do
user = random_user.()
_process_spec = ValueFlows.Simulate.fake_process_specification!(user)
res_spec = ValueFlows.Simulate.fake_resource_specification!(user)
# some proposed intents
action_id = ValueFlows.Simulate.action_id()
intent =
ValueFlows.Simulate.fake_intent!(user, %{
resource_conforms_to: res_spec,
action_id: action_id
})
proposal = ValueFlows.Simulate.fake_proposal!(user)
ValueFlows.Simulate.fake_proposed_to!(random_user.(), proposal)
ValueFlows.Simulate.fake_proposed_intent!(proposal, intent)
# define some geolocations
if(Bonfire.Common.Extend.extension_enabled?(Bonfire.Geolocate.Simulate)) do
places = for _ <- 1..2, do: Bonfire.Geolocate.Simulate.fake_geolocation!(random_user.())
random_place = fn -> Faker.Util.pick(places) end
for _ <- 1..2 do
# define some intents with geolocation
_intent =
ValueFlows.Simulate.fake_intent!(
random_user.(),
%{at_location: random_place.(), action_id: action_id}
)
# define some proposals with geolocation
_proposal =
ValueFlows.Simulate.fake_proposal!(user, %{eligible_location: random_place.()})
# both with geo
intent =
ValueFlows.Simulate.fake_intent!(
random_user.(),
%{at_location: random_place.(), action_id: action_id}
)
proposal = ValueFlows.Simulate.fake_proposal!(user, %{eligible_location: random_place.()})
ValueFlows.Simulate.fake_proposed_intent!(proposal, intent)
# some economic events
user = random_user.()
resource_inventoried_as =
ValueFlows.Simulate.fake_economic_resource!(user, %{current_location: random_place.()})
to_resource_inventoried_as =
ValueFlows.Simulate.fake_economic_resource!(random_user.(), %{
current_location: random_place.()
})
ValueFlows.Simulate.fake_economic_event!(
user,
%{
to_resource_inventoried_as: to_resource_inventoried_as.id,
resource_inventoried_as: resource_inventoried_as.id,
action: Faker.Util.pick(["transfer", "move"]),
at_location: random_place.()
}
)
end
end
if(Bonfire.Common.Extend.extension_enabled?(Bonfire.Quantify.Simulate)) do
unit1 = Bonfire.Quantify.Simulate.fake_unit!(random_user.())
unit2 = Bonfire.Quantify.Simulate.fake_unit!(random_user.())
for _ <- 1..2 do
action_id = ValueFlows.Simulate.action_id()
# define some intents with measurements
intent =
ValueFlows.Simulate.fake_intent!(
random_user.(),
%{action_id: action_id},
Faker.Util.pick([unit1, unit2])
)
proposal = ValueFlows.Simulate.fake_proposal!(user)
ValueFlows.Simulate.fake_proposed_intent!(proposal, intent)
# some economic events
user = random_user.()
unit = Faker.Util.pick([unit1, unit2])
resource_inventoried_as = ValueFlows.Simulate.fake_economic_resource!(user, %{}, unit)
to_resource_inventoried_as =
ValueFlows.Simulate.fake_economic_resource!(random_user.(), %{}, unit)
ValueFlows.Simulate.fake_economic_event!(
user,
%{
to_resource_inventoried_as: to_resource_inventoried_as.id,
resource_inventoried_as: resource_inventoried_as.id,
action: Faker.Util.pick(["transfer", "move"])
},
unit
)
end
end
end
end

View file

@ -123,15 +123,16 @@ if not Code.ensure_loaded?(Mess) do
end
end
defp read_umbrella(opts) do
path = "../../config/deps.path"
def read_umbrella(opts) do
config_dir = opts[:config_dir] || "../../config/"
path = "#{config_dir}deps.path"
if opts[:use_local_forks?] and File.exists?(path) do
[path, "../../config/deps.flavour.path"]
[path, "#{config_dir}deps.flavour.path"]
|> Enum.flat_map(&read(&1, :path))
|> Enum.flat_map(&dep_spec(&1, opts))
else
# IO.puts("did not load #{path}")
IO.warn("did not load #{path}")
[]
end
end