@seekrit/cli 0.37.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +308 -5
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1521,7 +1521,11 @@ const SYNC_PROVIDER_KINDS = [
|
|
|
1521
1521
|
"railway",
|
|
1522
1522
|
"aws-secrets-manager",
|
|
1523
1523
|
"aws-parameter-store",
|
|
1524
|
-
"render"
|
|
1524
|
+
"render",
|
|
1525
|
+
"fly",
|
|
1526
|
+
"northflank",
|
|
1527
|
+
"digitalocean",
|
|
1528
|
+
"heroku"
|
|
1525
1529
|
];
|
|
1526
1530
|
z.enum(SYNC_PROVIDER_KINDS);
|
|
1527
1531
|
/**
|
|
@@ -1658,6 +1662,55 @@ const awsParameterStoreConnectionConfigSchema = z.object({
|
|
|
1658
1662
|
* workspace it belongs to.
|
|
1659
1663
|
*/
|
|
1660
1664
|
const renderConnectionConfigSchema = z.object({ provider: z.literal("render") });
|
|
1665
|
+
/**
|
|
1666
|
+
* Fly.io account scope — empty, as Render's is.
|
|
1667
|
+
*
|
|
1668
|
+
* Neither half of "which account, which thing" needs stating: Fly app names are
|
|
1669
|
+
* globally unique, so the destination names its app and that is the whole
|
|
1670
|
+
* address. Nor is there a token kind to declare the way Railway's `tokenKind`
|
|
1671
|
+
* is — Fly's two token shapes do take different auth schemes, but
|
|
1672
|
+
* `flyAuthorization` in the connector reads which one from the token itself.
|
|
1673
|
+
*/
|
|
1674
|
+
const flyConnectionConfigSchema = z.object({ provider: z.literal("fly") });
|
|
1675
|
+
/**
|
|
1676
|
+
* A Northflank id — projects and secret groups are both slugs derived from the
|
|
1677
|
+
* name they were created with (`default-project`, `example-secret-group`), and
|
|
1678
|
+
* both appear in the resource's URL. Validated against Northflank's own pattern
|
|
1679
|
+
* so the common slip — pasting the *display name*, spaces and all — fails here
|
|
1680
|
+
* rather than as a bare 404 inside an alarm with nobody watching.
|
|
1681
|
+
*/
|
|
1682
|
+
const northflankIdSchema = z.string().trim().min(3).max(100).regex(/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/, "must be a Northflank ID — the slug in the resource's URL, not its display name");
|
|
1683
|
+
/**
|
|
1684
|
+
* Northflank account scope — deliberately empty.
|
|
1685
|
+
*
|
|
1686
|
+
* A Northflank API token is issued by exactly one team (or org-owned team) and
|
|
1687
|
+
* carries that scope itself; `GET /v1/auth` reports which. There is no team id
|
|
1688
|
+
* to disambiguate the way Vercel needs one, and no account id the way
|
|
1689
|
+
* Cloudflare does: the token plus the destination's project is the whole
|
|
1690
|
+
* address. The kind exists so the discriminated union stays uniform.
|
|
1691
|
+
*/
|
|
1692
|
+
const northflankConnectionConfigSchema = z.object({ provider: z.literal("northflank") });
|
|
1693
|
+
/**
|
|
1694
|
+
* DigitalOcean account scope — deliberately empty, as Render's and Fly's are.
|
|
1695
|
+
*
|
|
1696
|
+
* A DigitalOcean personal access token belongs to one account (or one team, if
|
|
1697
|
+
* it was issued inside one) and carries that scope itself, and every endpoint
|
|
1698
|
+
* this connector calls addresses its app by id. There is no team id to
|
|
1699
|
+
* disambiguate the way Vercel needs one: the token plus the destination's app
|
|
1700
|
+
* id is the whole address.
|
|
1701
|
+
*/
|
|
1702
|
+
const digitalOceanConnectionConfigSchema = z.object({ provider: z.literal("digitalocean") });
|
|
1703
|
+
/**
|
|
1704
|
+
* Heroku account scope — empty, as Fly's and Render's are.
|
|
1705
|
+
*
|
|
1706
|
+
* A Heroku API token carries its user's access to every app and team they can
|
|
1707
|
+
* reach, and app names are globally unique, so the destination's app is the
|
|
1708
|
+
* whole address. There is no team id to state: unlike Vercel, where a personal
|
|
1709
|
+
* token 403s a team-owned project without `teamId`, Heroku resolves
|
|
1710
|
+
* `/apps/{app_id_or_name}` against everything the token can see, team-owned or
|
|
1711
|
+
* not.
|
|
1712
|
+
*/
|
|
1713
|
+
const herokuConnectionConfigSchema = z.object({ provider: z.literal("heroku") });
|
|
1661
1714
|
const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
|
|
1662
1715
|
vercelConnectionConfigSchema,
|
|
1663
1716
|
cloudflareWorkersConnectionConfigSchema,
|
|
@@ -1666,7 +1719,11 @@ const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
|
|
|
1666
1719
|
railwayConnectionConfigSchema,
|
|
1667
1720
|
awsSecretsManagerConnectionConfigSchema,
|
|
1668
1721
|
awsParameterStoreConnectionConfigSchema,
|
|
1669
|
-
renderConnectionConfigSchema
|
|
1722
|
+
renderConnectionConfigSchema,
|
|
1723
|
+
flyConnectionConfigSchema,
|
|
1724
|
+
northflankConnectionConfigSchema,
|
|
1725
|
+
digitalOceanConnectionConfigSchema,
|
|
1726
|
+
herokuConnectionConfigSchema
|
|
1670
1727
|
]);
|
|
1671
1728
|
/** Vercel's three deployment targets. A binding writes to one or more. */
|
|
1672
1729
|
const VERCEL_TARGETS = [
|
|
@@ -1867,6 +1924,160 @@ const renderEnvGroupDestinationSchema = z.object({
|
|
|
1867
1924
|
envGroupId: renderEnvGroupIdSchema
|
|
1868
1925
|
});
|
|
1869
1926
|
const renderDestinationSchema = z.discriminatedUnion("kind", [renderServiceDestinationSchema, renderEnvGroupDestinationSchema]);
|
|
1927
|
+
/**
|
|
1928
|
+
* The Fly app whose secret set a binding owns.
|
|
1929
|
+
*
|
|
1930
|
+
* A Fly app has **one** secret set, shared by every Machine in every region —
|
|
1931
|
+
* there is no per-target split to state, the way Vercel and Pages have one.
|
|
1932
|
+
* Fly's convention is that staging and production are separate *apps*
|
|
1933
|
+
* (`storefront`, `storefront-staging`), so pointing at an environment means
|
|
1934
|
+
* naming that app, exactly as a Wrangler environment means naming its own
|
|
1935
|
+
* Worker.
|
|
1936
|
+
*
|
|
1937
|
+
* Values land **staged**: Fly injects secrets when a Machine boots, so already
|
|
1938
|
+
* running Machines keep what they started with until the app is deployed or its
|
|
1939
|
+
* Machines are updated (`fly secrets deploy -a <app>`), while Machines created
|
|
1940
|
+
* after the push get them straight away. The connector deliberately restarts
|
|
1941
|
+
* nothing — see the note in `apps/api/src/lib/sync/connectors/fly.ts`.
|
|
1942
|
+
*/
|
|
1943
|
+
const flyDestinationSchema = z.object({
|
|
1944
|
+
provider: z.literal("fly"),
|
|
1945
|
+
/** Fly app name, as `fly apps list` prints it. */
|
|
1946
|
+
appName: z.string().trim().min(1).max(63).regex(/^[a-z0-9][a-z0-9-]*$/, "must be a Fly app name (lowercase letters, numbers, and dashes)")
|
|
1947
|
+
});
|
|
1948
|
+
/**
|
|
1949
|
+
* Where inside Northflank a binding writes: one **secret group** in one
|
|
1950
|
+
* project.
|
|
1951
|
+
*
|
|
1952
|
+
* A secret group is Northflank's unit of injection — services and jobs in the
|
|
1953
|
+
* project inherit its variables, subject to the group's own restrictions and
|
|
1954
|
+
* priority. Those settings belong to the operator, not to seekrit: a binding
|
|
1955
|
+
* names an existing group and only ever writes its `variables` map, so
|
|
1956
|
+
* restrictions, priority, secret type, and any secret *files* stay as they were
|
|
1957
|
+
* configured.
|
|
1958
|
+
*
|
|
1959
|
+
* There is no environment field. Northflank has no per-group environment axis —
|
|
1960
|
+
* separate environments are separate projects (or separate groups restricted to
|
|
1961
|
+
* a stage), so the binding's seekrit environment maps to a group, one to one.
|
|
1962
|
+
*/
|
|
1963
|
+
const northflankDestinationSchema = z.object({
|
|
1964
|
+
provider: z.literal("northflank"),
|
|
1965
|
+
/** Project id — the slug in the project URL (`default-project`). */
|
|
1966
|
+
projectId: northflankIdSchema,
|
|
1967
|
+
/** Secret group id — the slug in the group's URL (`example-secret-group`). */
|
|
1968
|
+
secretGroupId: northflankIdSchema
|
|
1969
|
+
});
|
|
1970
|
+
/**
|
|
1971
|
+
* When App Platform makes a variable visible. DigitalOcean's enum also has
|
|
1972
|
+
* `UNSET`, which is not offered: it means "no scope stated", and a secrets
|
|
1973
|
+
* manager that writes a value should say when that value applies.
|
|
1974
|
+
*
|
|
1975
|
+
* The default here is `RUN_TIME` rather than DigitalOcean's own
|
|
1976
|
+
* `RUN_AND_BUILD_TIME`, and the difference is deliberate. A build-time variable
|
|
1977
|
+
* is visible to every build command, every buildpack, and anything they print;
|
|
1978
|
+
* a secret only the running process needs has no business being there. Binding
|
|
1979
|
+
* a value a build genuinely needs — a private registry token, a sourcemap
|
|
1980
|
+
* upload key — is a decision worth making explicitly.
|
|
1981
|
+
*/
|
|
1982
|
+
const DIGITALOCEAN_ENV_SCOPES = [
|
|
1983
|
+
"RUN_TIME",
|
|
1984
|
+
"BUILD_TIME",
|
|
1985
|
+
"RUN_AND_BUILD_TIME"
|
|
1986
|
+
];
|
|
1987
|
+
/**
|
|
1988
|
+
* A DigitalOcean app id — the UUID in the app's dashboard URL
|
|
1989
|
+
* (`cloud.digitalocean.com/apps/<id>`), and what `doctl apps list` prints.
|
|
1990
|
+
*
|
|
1991
|
+
* DigitalOcean's own spec types this as a bare string, but every app id it
|
|
1992
|
+
* issues is a UUID, and the slip worth catching is the one the API cannot tell
|
|
1993
|
+
* from a typo: pasting the app's *name* (`storefront`), which `GET /v2/apps/{id}`
|
|
1994
|
+
* answers with a flat 404 hours later inside an alarm, with nobody watching.
|
|
1995
|
+
*/
|
|
1996
|
+
const digitalOceanAppIdSchema = z.string().trim().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, "must be a DigitalOcean app ID (the UUID in the app's URL), not its name");
|
|
1997
|
+
/**
|
|
1998
|
+
* A component name, matching App Platform's own pattern for one. Names are
|
|
1999
|
+
* unique within an app, which is what makes a name — rather than an index into
|
|
2000
|
+
* `services` — the stable way to address a component's variables.
|
|
2001
|
+
*/
|
|
2002
|
+
const digitalOceanComponentNameSchema = z.string().trim().regex(/^[a-z][a-z0-9-]{0,30}[a-z0-9]$/, "must be an App Platform component name (lowercase letters, numbers, and dashes)");
|
|
2003
|
+
/**
|
|
2004
|
+
* Where inside a DigitalOcean app a binding writes.
|
|
2005
|
+
*
|
|
2006
|
+
* ## A push is a deployment
|
|
2007
|
+
*
|
|
2008
|
+
* App Platform has no per-variable endpoint. Environment variables live in the
|
|
2009
|
+
* app spec, and the only way to change one is to submit a new spec — which
|
|
2010
|
+
* starts a **new deployment** of the app. That is not a side effect this
|
|
2011
|
+
* connector chose and there is no flag to suppress it; it is what "set an
|
|
2012
|
+
* environment variable" means on this platform, in the control panel and in
|
|
2013
|
+
* `doctl` alike.
|
|
2014
|
+
*
|
|
2015
|
+
* The deployment reuses each component's current source (seekrit never sends
|
|
2016
|
+
* `update_all_source_versions`), so it redeploys the code already running
|
|
2017
|
+
* rather than pulling a newer commit or image. It is still a real deployment:
|
|
2018
|
+
* a build, a health check, and a rollout. Bind an environment here knowing that
|
|
2019
|
+
* changing a secret in it will roll the app.
|
|
2020
|
+
*
|
|
2021
|
+
* ## Values are written encrypted
|
|
2022
|
+
*
|
|
2023
|
+
* Everything seekrit writes goes in as `type: SECRET`, so App Platform encrypts
|
|
2024
|
+
* it at rest and hands it back as an opaque `EV[1:…]` blob rather than as
|
|
2025
|
+
* plaintext. That is also why this connector cannot tell whether a value it is
|
|
2026
|
+
* about to write is already there — see
|
|
2027
|
+
* `apps/api/src/lib/sync/connectors/digitalocean.ts`.
|
|
2028
|
+
*/
|
|
2029
|
+
const digitalOceanAppDestinationSchema = z.object({
|
|
2030
|
+
provider: z.literal("digitalocean"),
|
|
2031
|
+
kind: z.literal("app"),
|
|
2032
|
+
/** App id — the UUID in `cloud.digitalocean.com/apps/<id>`. */
|
|
2033
|
+
appId: digitalOceanAppIdSchema,
|
|
2034
|
+
scope: z.enum(DIGITALOCEAN_ENV_SCOPES).default("RUN_TIME")
|
|
2035
|
+
});
|
|
2036
|
+
/**
|
|
2037
|
+
* One component's own environment variables. Narrower than the app-level list:
|
|
2038
|
+
* only this service, worker, job, static site, or function sees them, and a key
|
|
2039
|
+
* here wins over the same key at app level.
|
|
2040
|
+
*/
|
|
2041
|
+
const digitalOceanComponentDestinationSchema = z.object({
|
|
2042
|
+
provider: z.literal("digitalocean"),
|
|
2043
|
+
kind: z.literal("component"),
|
|
2044
|
+
appId: digitalOceanAppIdSchema,
|
|
2045
|
+
/** Component name, as it appears in the app spec — not its type. */
|
|
2046
|
+
componentName: digitalOceanComponentNameSchema,
|
|
2047
|
+
scope: z.enum(DIGITALOCEAN_ENV_SCOPES).default("RUN_TIME")
|
|
2048
|
+
});
|
|
2049
|
+
const digitalOceanDestinationSchema = z.discriminatedUnion("kind", [digitalOceanAppDestinationSchema, digitalOceanComponentDestinationSchema]);
|
|
2050
|
+
/**
|
|
2051
|
+
* A Heroku app, named the way `/apps/{app_id_or_name}` names one: either the
|
|
2052
|
+
* app name or its UUID id. Both are accepted because both work, and the id is
|
|
2053
|
+
* the durable one — renaming an app in the dashboard breaks a binding that
|
|
2054
|
+
* holds its name, and does not break one that holds its id.
|
|
2055
|
+
*
|
|
2056
|
+
* The name pattern is Heroku's own (`^[a-z][a-z0-9-]{1,28}[a-z0-9]$`): 3–30
|
|
2057
|
+
* characters, starting with a letter and ending alphanumeric. Checking it here
|
|
2058
|
+
* turns the habitual slip — pasting `example.herokuapp.com`, or a name with
|
|
2059
|
+
* capitals — into a message at the form rather than a bare 404 from an alarm
|
|
2060
|
+
* with nobody watching.
|
|
2061
|
+
*/
|
|
2062
|
+
const herokuAppSchema = z.string().trim().refine((value) => /^[a-z][a-z0-9-]{1,28}[a-z0-9]$/.test(value) || /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value), "must be a Heroku app name (lowercase letters, numbers, and dashes) or its UUID");
|
|
2063
|
+
/**
|
|
2064
|
+
* The Heroku app whose config vars a binding owns.
|
|
2065
|
+
*
|
|
2066
|
+
* A Heroku app has **one** set of config vars, shared by every dyno and every
|
|
2067
|
+
* process type — there is no per-target split to state the way Vercel and Pages
|
|
2068
|
+
* have one. Heroku's convention is that staging and production are separate
|
|
2069
|
+
* *apps* (`storefront`, `storefront-staging`), so pointing at an environment
|
|
2070
|
+
* means naming that app, exactly as it does on Fly.
|
|
2071
|
+
*
|
|
2072
|
+
* Unlike Fly, values take effect **immediately**: setting config vars cuts a new
|
|
2073
|
+
* release and restarts the app's dynos, which is why a run sends exactly one
|
|
2074
|
+
* request — see the note in `apps/api/src/lib/sync/connectors/heroku.ts`.
|
|
2075
|
+
*/
|
|
2076
|
+
const herokuDestinationSchema = z.object({
|
|
2077
|
+
provider: z.literal("heroku"),
|
|
2078
|
+
/** App name as `heroku apps` prints it, or the app's UUID. */
|
|
2079
|
+
app: herokuAppSchema
|
|
2080
|
+
});
|
|
1870
2081
|
const syncDestinationSchema = z.discriminatedUnion("provider", [
|
|
1871
2082
|
vercelDestinationSchema,
|
|
1872
2083
|
cloudflareWorkersDestinationSchema,
|
|
@@ -1875,7 +2086,11 @@ const syncDestinationSchema = z.discriminatedUnion("provider", [
|
|
|
1875
2086
|
railwayDestinationSchema,
|
|
1876
2087
|
awsSecretsManagerDestinationSchema,
|
|
1877
2088
|
awsParameterStoreDestinationSchema,
|
|
1878
|
-
renderDestinationSchema
|
|
2089
|
+
renderDestinationSchema,
|
|
2090
|
+
flyDestinationSchema,
|
|
2091
|
+
northflankDestinationSchema,
|
|
2092
|
+
digitalOceanDestinationSchema,
|
|
2093
|
+
herokuDestinationSchema
|
|
1879
2094
|
]);
|
|
1880
2095
|
/**
|
|
1881
2096
|
* How seekrit secret names become destination key names. Applied in order:
|
|
@@ -3080,7 +3295,7 @@ function isCliSessionToken(value) {
|
|
|
3080
3295
|
}
|
|
3081
3296
|
//#endregion
|
|
3082
3297
|
//#region package.json
|
|
3083
|
-
var version = "0.
|
|
3298
|
+
var version = "0.39.0";
|
|
3084
3299
|
//#endregion
|
|
3085
3300
|
//#region ../../packages/api-client/src/index.ts
|
|
3086
3301
|
var SeekritApiError = class extends Error {
|
|
@@ -6908,6 +7123,51 @@ function assertMember(value, allowed, flag, fallback) {
|
|
|
6908
7123
|
if (!allowed.includes(value)) fail(`unknown ${flag} "${value}" — one of: ${allowed.join(", ")}`);
|
|
6909
7124
|
return value;
|
|
6910
7125
|
}
|
|
7126
|
+
/**
|
|
7127
|
+
* Fly app names are DNS labels, and the two habitual slips are pasting the
|
|
7128
|
+
* hostname (`storefront.fly.dev`) or a name with capitals in it. Both are a 404
|
|
7129
|
+
* from Fly much later, so they are caught here with a message that says which.
|
|
7130
|
+
*/
|
|
7131
|
+
function assertFlyApp(value) {
|
|
7132
|
+
if (!value) fail("--fly-app is required for fly (the app name, e.g. storefront-production)");
|
|
7133
|
+
const appName = value.trim();
|
|
7134
|
+
if (/\.(fly\.dev|internal)$/.test(appName)) fail(`--fly-app takes the app name, not its hostname — try "${appName.split(".")[0]}"`);
|
|
7135
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(appName) || appName.length > 63) fail(`--fly-app "${appName}" is not a Fly app name (lowercase letters, numbers, and dashes)`);
|
|
7136
|
+
return appName;
|
|
7137
|
+
}
|
|
7138
|
+
/**
|
|
7139
|
+
* Heroku takes an app name or the app's UUID, so both are accepted here. The
|
|
7140
|
+
* habitual slips are the hostname (`storefront.herokuapp.com`) and capitals;
|
|
7141
|
+
* both are a 404 from Heroku much later, so they are caught here with a message
|
|
7142
|
+
* that says which.
|
|
7143
|
+
*/
|
|
7144
|
+
function assertHerokuApp(value) {
|
|
7145
|
+
if (!value) fail("--heroku-app is required for heroku (the app name, e.g. storefront-production)");
|
|
7146
|
+
const app = value.trim();
|
|
7147
|
+
if (/\.(herokuapp\.com|herokudns\.com)$/.test(app)) fail(`--heroku-app takes the app name, not its hostname — try "${app.split(".")[0]}"`);
|
|
7148
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(app) && !/^[a-z][a-z0-9-]{1,28}[a-z0-9]$/.test(app)) fail(`--heroku-app "${app}" is not a Heroku app name — 3 to 30 characters, starting with a lowercase letter, made of lowercase letters, numbers, and dashes (or the app's UUID)`);
|
|
7149
|
+
return app;
|
|
7150
|
+
}
|
|
7151
|
+
/**
|
|
7152
|
+
* Northflank ids are slugs from the resource's URL, so the slip to catch is the
|
|
7153
|
+
* *display name* — "App Secrets" where "app-secrets" belongs.
|
|
7154
|
+
*/
|
|
7155
|
+
function assertNorthflankId(value, flag) {
|
|
7156
|
+
if (!value) fail(`${flag} is required for northflank (the slug from its URL)`);
|
|
7157
|
+
const id = value.trim();
|
|
7158
|
+
if (!/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/.test(id)) fail(`${flag} should be a Northflank ID like "app-secrets", not "${id}"`);
|
|
7159
|
+
return id;
|
|
7160
|
+
}
|
|
7161
|
+
/**
|
|
7162
|
+
* DigitalOcean app ids are UUIDs, and the slip to catch is the app *name* —
|
|
7163
|
+
* what the dashboard headline shows, and what the API answers with a bare 404.
|
|
7164
|
+
*/
|
|
7165
|
+
function assertDigitalOceanApp(value) {
|
|
7166
|
+
if (!value) fail("--do-app is required for digitalocean (the UUID in the app's URL)");
|
|
7167
|
+
const appId = value.trim();
|
|
7168
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(appId)) fail(`--do-app should be the app's UUID, from its URL — not "${appId}"`);
|
|
7169
|
+
return appId;
|
|
7170
|
+
}
|
|
6911
7171
|
function assertProvider(value) {
|
|
6912
7172
|
if (!SYNC_PROVIDER_KINDS.includes(value)) fail(`unknown provider "${value}" — one of: ${SYNC_PROVIDER_KINDS.join(", ")}`);
|
|
6913
7173
|
return value;
|
|
@@ -6945,6 +7205,10 @@ function buildConfig(provider, options) {
|
|
|
6945
7205
|
accessKeyId: options.accessKeyId
|
|
6946
7206
|
};
|
|
6947
7207
|
case "render": return { provider: "render" };
|
|
7208
|
+
case "fly": return { provider: "fly" };
|
|
7209
|
+
case "northflank": return { provider: "northflank" };
|
|
7210
|
+
case "digitalocean": return { provider: "digitalocean" };
|
|
7211
|
+
case "heroku": return { provider: "heroku" };
|
|
6948
7212
|
}
|
|
6949
7213
|
}
|
|
6950
7214
|
/** Where inside the platform a binding writes. */
|
|
@@ -7034,6 +7298,38 @@ function buildDestination(provider, options) {
|
|
|
7034
7298
|
};
|
|
7035
7299
|
return fail("--service (srv-…, or crn-… for a cron job) or --env-group (evg-…) is required for render");
|
|
7036
7300
|
}
|
|
7301
|
+
case "fly": return {
|
|
7302
|
+
provider: "fly",
|
|
7303
|
+
appName: assertFlyApp(options.flyApp)
|
|
7304
|
+
};
|
|
7305
|
+
case "northflank": return {
|
|
7306
|
+
provider: "northflank",
|
|
7307
|
+
projectId: assertNorthflankId(options.project, "--project"),
|
|
7308
|
+
secretGroupId: assertNorthflankId(options.secretGroup, "--secret-group")
|
|
7309
|
+
};
|
|
7310
|
+
case "digitalocean": {
|
|
7311
|
+
const appId = assertDigitalOceanApp(options.doApp);
|
|
7312
|
+
const scope = assertMember(options.envScope, DIGITALOCEAN_ENV_SCOPES, "--env-scope", "RUN_TIME");
|
|
7313
|
+
const componentName = options.component?.trim();
|
|
7314
|
+
if (!componentName) return {
|
|
7315
|
+
provider: "digitalocean",
|
|
7316
|
+
kind: "app",
|
|
7317
|
+
appId,
|
|
7318
|
+
scope
|
|
7319
|
+
};
|
|
7320
|
+
if (!/^[a-z][a-z0-9-]{0,30}[a-z0-9]$/.test(componentName)) fail(`--component "${componentName}" is not an App Platform component name (lowercase letters, numbers, and dashes)`);
|
|
7321
|
+
return {
|
|
7322
|
+
provider: "digitalocean",
|
|
7323
|
+
kind: "component",
|
|
7324
|
+
appId,
|
|
7325
|
+
componentName,
|
|
7326
|
+
scope
|
|
7327
|
+
};
|
|
7328
|
+
}
|
|
7329
|
+
case "heroku": return {
|
|
7330
|
+
provider: "heroku",
|
|
7331
|
+
app: assertHerokuApp(options.herokuApp)
|
|
7332
|
+
};
|
|
7037
7333
|
}
|
|
7038
7334
|
}
|
|
7039
7335
|
/** One-line description of a destination, for list output. */
|
|
@@ -7047,14 +7343,21 @@ function describeDestination(destination) {
|
|
|
7047
7343
|
case "aws-secrets-manager": return destination.layout === "json-bundle" ? `${destination.secretName} (json bundle)` : `${destination.pathPrefix ?? ""}* (secret per name)`;
|
|
7048
7344
|
case "aws-parameter-store": return `${destination.path}* (${destination.type})`;
|
|
7049
7345
|
case "render": return destination.kind === "service" ? destination.serviceId : `env group ${destination.envGroupId}`;
|
|
7346
|
+
case "fly": return destination.appName;
|
|
7347
|
+
case "northflank": return `${destination.projectId} / ${destination.secretGroupId}`;
|
|
7348
|
+
case "digitalocean": return `${destination.appId}${destination.kind === "component" ? ` / ${destination.componentName}` : ""} (${destination.scope})`;
|
|
7349
|
+
case "heroku": return destination.app;
|
|
7050
7350
|
}
|
|
7051
7351
|
}
|
|
7052
7352
|
/**
|
|
7053
7353
|
* Destination flags, shared by `verify` and `enable` so the two can never drift
|
|
7054
7354
|
* into accepting different ways of naming the same destination.
|
|
7355
|
+
*
|
|
7356
|
+
* Fly's is `--fly-app`, not `--app`: `--app` already names the seekrit
|
|
7357
|
+
* application whose environment the binding reads from.
|
|
7055
7358
|
*/
|
|
7056
7359
|
function destinationOptions(command) {
|
|
7057
|
-
return command.option("--project <id>", "vercel: project id or name · cloudflare-pages: project name").option("--target <list>", "vercel / cloudflare-pages: comma-separated deployment targets", "production").option("--git-branch <branch>", "vercel: restrict preview writes to one branch").option("--script <name>", "cloudflare-workers: Worker script name").option("--store-id <id>", "cloudflare-secrets-store: store ID (32 hex)").option("--scopes <list>", "cloudflare-secrets-store: comma-separated scopes", "workers").option("--railway-project <id>", "railway: project ID (a UUID)").option("--railway-environment <id>", "railway: environment ID (a UUID)").option("--service <id>", "railway: service ID (omit for the environment's shared variables) · render: service ID (srv-…, or crn-… for a cron job)").option("--skip-deploys", "railway: stage values without triggering a redeploy").option("--path <path>", "aws-parameter-store: hierarchy, e.g. /prod/storefront/ · aws-secrets-manager: name prefix").option("--layout <layout>", `aws-secrets-manager: ${AWS_SECRETS_MANAGER_LAYOUTS.join(" | ")}`).option("--secret-name <name>", "aws-secrets-manager: the secret a json-bundle writes to").option("--param-type <type>", `aws-parameter-store: ${AWS_PARAMETER_TYPES.join(" | ")}`).option("--tier <tier>", `aws-parameter-store: ${AWS_PARAMETER_TIERS.join(" | ")}`).option("--kms-key-id <id>", "aws: customer-managed KMS key id, ARN, or alias").option("--env-group <id>", "render: environment group ID (evg-…)");
|
|
7360
|
+
return command.option("--project <id>", "vercel: project id or name · cloudflare-pages / northflank: project name or slug").option("--target <list>", "vercel / cloudflare-pages: comma-separated deployment targets", "production").option("--git-branch <branch>", "vercel: restrict preview writes to one branch").option("--script <name>", "cloudflare-workers: Worker script name").option("--store-id <id>", "cloudflare-secrets-store: store ID (32 hex)").option("--scopes <list>", "cloudflare-secrets-store: comma-separated scopes", "workers").option("--railway-project <id>", "railway: project ID (a UUID)").option("--railway-environment <id>", "railway: environment ID (a UUID)").option("--service <id>", "railway: service ID (omit for the environment's shared variables) · render: service ID (srv-…, or crn-… for a cron job)").option("--skip-deploys", "railway: stage values without triggering a redeploy").option("--path <path>", "aws-parameter-store: hierarchy, e.g. /prod/storefront/ · aws-secrets-manager: name prefix").option("--layout <layout>", `aws-secrets-manager: ${AWS_SECRETS_MANAGER_LAYOUTS.join(" | ")}`).option("--secret-name <name>", "aws-secrets-manager: the secret a json-bundle writes to").option("--param-type <type>", `aws-parameter-store: ${AWS_PARAMETER_TYPES.join(" | ")}`).option("--tier <tier>", `aws-parameter-store: ${AWS_PARAMETER_TIERS.join(" | ")}`).option("--kms-key-id <id>", "aws: customer-managed KMS key id, ARN, or alias").option("--env-group <id>", "render: environment group ID (evg-…)").option("--fly-app <name>", "fly: app name, as `fly apps list` shows it").option("--secret-group <id>", "northflank: secret group ID (the slug in its URL)").option("--do-app <id>", "digitalocean: App Platform app ID (the UUID in its URL)").option("--component <name>", "digitalocean: write to one component's variables (omit for app-level)").option("--env-scope <scope>", `digitalocean: ${DIGITALOCEAN_ENV_SCOPES.join(" | ")}`).option("--heroku-app <name>", "heroku: app name, as `heroku apps` shows it (or its UUID)");
|
|
7058
7361
|
}
|
|
7059
7362
|
/** Find a connection by id or name — nobody keeps `syc_…` ids in their head. */
|
|
7060
7363
|
async function resolveConnection(ctx, orgId, ref) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seekrit/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"@types/node": "^26.1.0",
|
|
28
28
|
"tsdown": "^0.22.3",
|
|
29
29
|
"vitest": "^4.1.9",
|
|
30
|
-
"@seekrit/
|
|
30
|
+
"@seekrit/core": "0.0.1",
|
|
31
31
|
"@seekrit/crypto": "0.0.1",
|
|
32
|
-
"@seekrit/
|
|
32
|
+
"@seekrit/api-client": "0.0.1"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsdown",
|