Skip to content

Packages

Add a dependency

AI-assistedThis page includes both human and AI contributions.

How to make a Package import another, from the same project, your local store, or a GitHub repository, and what the dependency adds to what the Package requires and to the Blueprint.

A Package often builds on another. Acme’s support Package apologizes to a customer with a credit, so it imports the billing Package and doesn’t call Stripe again. A Package that reads a web page imports the curated Jina Package. The build has to know where each import comes from. The Blueprint needs rules for every Package in the chain, including the ones the program doesn’t import directly, and add-package writes them.

This guide shows you how to make a Package import another, and what the dependency adds to the Blueprint. The example is @acme/support, which imports @acme/billing. Substitute your Packages.

Start with the billing Package from Export a function, including its Stripe implementation. The final run needs a Stripe test-mode key and a customer in the same test account.

@acme/support is the second Package of the project, added with submilli build new @acme/support packages/support, as shown in Start a project. Its one operation credits the customer through the billing Package:

packages/support/src/lib.ts
import { applyCredit } from "@acme/billing";
/**
* Apologize to a customer with a goodwill credit, and say so in dollars.
* @param customerId The customer's id in the billing system.
* @returns A sentence saying how much was credited.
*/
export function apologize(customerId: string): string {
const credit = applyCredit(customerId, 1500);
return `credited $${(credit.amount / 100).toString()}`;
}

An import submilli.toml doesn’t declare stops the build:

Terminal window
submilli build check -p @acme/support
error: package `@acme/billing` not found
--> packages/support/src/lib.ts:1:29
|
1 | import { applyCredit } from "@acme/billing";
| ^^^^^^^^^^^^^^^
The dependency is Declare it
Another Package in the project In the Package’s dependencies
A Package in the local store There, and in [dependencies] with its version
A Package in a GitHub repository There, and in [dependencies] as { github = "github.com/org/repo", rev = "<commit>" }
A Package in a private GitHub repository The same, and whoever builds needs a GitHub token that can read it

The billing Package is a sibling, so one line in the support Package’s block declares it:

submilli.toml (fragment)
[[package]]
name = "@acme/support"
version = "0.1.0"
description = "Goodwill credits through Acme's billing package."
path = "packages/support"
dependencies = ["@acme/billing"]
Terminal window
submilli build check -p @acme/support
checked @acme/billing v0.1.0
checked @acme/support v0.1.0

-p builds the Package and the siblings it depends on, in order. A Package from the local store or from GitHub is declared at the top of submilli.toml as well, and named in the Package’s list the same way:

The following fragment illustrates those other sources. Keep the sibling-only declaration for this guide. To use the fragment in your own project, install @submilli/jina v0.1.0 in the local store and replace the CRM repository and commit with your own.

submilli.toml (fragment)
[dependencies]
"@submilli/jina" = "0.1.0"
"@acme/crm" = { github = "github.com/acme/crm-package", rev = "9c1f2e4a7d3b06e15a8c4f2d7b9e1a3c5f7d9b2e" }
[[package]]
name = "@acme/support"
version = "0.1.0"
description = "Goodwill credits through Acme's billing package."
path = "packages/support"
dependencies = ["@acme/billing", "@submilli/jina", "@acme/crm"]

A GitHub dependency is fetched into the local store by the build, which records the commits it used in submilli.lock. rev is the full 40-character commit SHA. A branch, tag, or short SHA is refused. A private one, such as @acme/crm above, is fetched with the GitHub token of whoever builds or installs. That is your token on your machine (submilli github authenticate) and the server’s token on a server, as Install private Packages explains. The token needs Contents: Read-only on every private repository in the dependencies, including the ones your dependencies depend on.

What a Package uses of another shows up in what it requires. The build derived this for the support Package:

packages/support/capabilities.yaml
namespace: acme
provides: []
requires:
- capability: acme.com/credits.apply

A Blueprint grants that to @acme/support as it would to a program. The billing Package makes its own calls, as the caller @acme/billing, so it needs rules too, and so does the secret it reads. add-package adds rules for the chain. First publish the support Package and its sibling dependency to the local store. build check installs neither:

Terminal window
submilli build publish-local -p @acme/support
installed @acme/billing v0.1.0 -> …/packages/@acme/billing
installed @acme/support v0.1.0 -> …/packages/@acme/support

Then create the Blueprint and add the Package:

Terminal window
submilli blueprint init support
submilli blueprint add-package @acme/support --no-capabilities
✓ created blueprint.yaml (name: support)
warning: blueprint.yaml: package `@acme/billing` requires secret `BILLING_API_KEY`, but `secrets:` does not declare it
✓ added @acme/support to blueprint.yaml
added 1 rules to caller `@acme/support` (default allow):
allow acme.com/credits.apply
✓ added caller rules for @acme/billing, a dependency of @acme/support
added 3 rules to caller `@acme/billing` (default allow):
allow http.get (filter: host == "api.stripe.com")
allow http.post (filter: host == "api.stripe.com")
allow secrets.get (filter: name == "BILLING_API_KEY")

Each Package in the chain gets its own caller list from what it requires, but only the Package you named is listed under packages:, the Packages a program may import. A program can credit a customer only through apologize, which fixes the amount. Declare the secret the warning names, and put your Stripe test-mode key in the store if it isn’t there yet:

Terminal window
submilli blueprint secret add BILLING_API_KEY --store billing_api_key
submilli secret put billing_api_key
✓ declared secret 'BILLING_API_KEY' (store: billing_api_key) in blueprint.yaml
Value for 'billing_api_key': [hidden]
Stored secret 'billing_api_key'

Create apology.ts. Replace the customer id with a customer from the Stripe test account that owns your key. Each run applies a $15 test credit:

apology.ts
import { apologize } from "@acme/support";
function main(): string {
return apologize("cus_VMQR3azuTWVAWs");
}
Terminal window
submilli run --blueprint blueprint.yaml apology.ts
credited $15

The program called the support Package, the support Package called the billing Package, and the billing Package called Stripe, each under its own rules. The complete Blueprint:

blueprint.yaml
kind: blueprint
name: support
secrets:
BILLING_API_KEY:
store: billing_api_key
packages:
- '@acme/support'
default: deny
permissions:
'@acme/billing':
- capability: http.get
filter: host == "api.stripe.com"
action: allow
- capability: http.post
filter: host == "api.stripe.com"
action: allow
- capability: secrets.get
filter: name == "BILLING_API_KEY"
action: allow
'@acme/support':
- capability: acme.com/credits.apply
action: allow
main: []