@seekrit/cli 0.37.0 → 0.38.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 +120 -5
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1521,7 +1521,9 @@ 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"
|
|
1525
1527
|
];
|
|
1526
1528
|
z.enum(SYNC_PROVIDER_KINDS);
|
|
1527
1529
|
/**
|
|
@@ -1658,6 +1660,34 @@ const awsParameterStoreConnectionConfigSchema = z.object({
|
|
|
1658
1660
|
* workspace it belongs to.
|
|
1659
1661
|
*/
|
|
1660
1662
|
const renderConnectionConfigSchema = z.object({ provider: z.literal("render") });
|
|
1663
|
+
/**
|
|
1664
|
+
* Fly.io account scope — empty, as Render's is.
|
|
1665
|
+
*
|
|
1666
|
+
* Neither half of "which account, which thing" needs stating: Fly app names are
|
|
1667
|
+
* globally unique, so the destination names its app and that is the whole
|
|
1668
|
+
* address. Nor is there a token kind to declare the way Railway's `tokenKind`
|
|
1669
|
+
* is — Fly's two token shapes do take different auth schemes, but
|
|
1670
|
+
* `flyAuthorization` in the connector reads which one from the token itself.
|
|
1671
|
+
*/
|
|
1672
|
+
const flyConnectionConfigSchema = z.object({ provider: z.literal("fly") });
|
|
1673
|
+
/**
|
|
1674
|
+
* A Northflank id — projects and secret groups are both slugs derived from the
|
|
1675
|
+
* name they were created with (`default-project`, `example-secret-group`), and
|
|
1676
|
+
* both appear in the resource's URL. Validated against Northflank's own pattern
|
|
1677
|
+
* so the common slip — pasting the *display name*, spaces and all — fails here
|
|
1678
|
+
* rather than as a bare 404 inside an alarm with nobody watching.
|
|
1679
|
+
*/
|
|
1680
|
+
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");
|
|
1681
|
+
/**
|
|
1682
|
+
* Northflank account scope — deliberately empty.
|
|
1683
|
+
*
|
|
1684
|
+
* A Northflank API token is issued by exactly one team (or org-owned team) and
|
|
1685
|
+
* carries that scope itself; `GET /v1/auth` reports which. There is no team id
|
|
1686
|
+
* to disambiguate the way Vercel needs one, and no account id the way
|
|
1687
|
+
* Cloudflare does: the token plus the destination's project is the whole
|
|
1688
|
+
* address. The kind exists so the discriminated union stays uniform.
|
|
1689
|
+
*/
|
|
1690
|
+
const northflankConnectionConfigSchema = z.object({ provider: z.literal("northflank") });
|
|
1661
1691
|
const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
|
|
1662
1692
|
vercelConnectionConfigSchema,
|
|
1663
1693
|
cloudflareWorkersConnectionConfigSchema,
|
|
@@ -1666,7 +1696,9 @@ const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
|
|
|
1666
1696
|
railwayConnectionConfigSchema,
|
|
1667
1697
|
awsSecretsManagerConnectionConfigSchema,
|
|
1668
1698
|
awsParameterStoreConnectionConfigSchema,
|
|
1669
|
-
renderConnectionConfigSchema
|
|
1699
|
+
renderConnectionConfigSchema,
|
|
1700
|
+
flyConnectionConfigSchema,
|
|
1701
|
+
northflankConnectionConfigSchema
|
|
1670
1702
|
]);
|
|
1671
1703
|
/** Vercel's three deployment targets. A binding writes to one or more. */
|
|
1672
1704
|
const VERCEL_TARGETS = [
|
|
@@ -1867,6 +1899,49 @@ const renderEnvGroupDestinationSchema = z.object({
|
|
|
1867
1899
|
envGroupId: renderEnvGroupIdSchema
|
|
1868
1900
|
});
|
|
1869
1901
|
const renderDestinationSchema = z.discriminatedUnion("kind", [renderServiceDestinationSchema, renderEnvGroupDestinationSchema]);
|
|
1902
|
+
/**
|
|
1903
|
+
* The Fly app whose secret set a binding owns.
|
|
1904
|
+
*
|
|
1905
|
+
* A Fly app has **one** secret set, shared by every Machine in every region —
|
|
1906
|
+
* there is no per-target split to state, the way Vercel and Pages have one.
|
|
1907
|
+
* Fly's convention is that staging and production are separate *apps*
|
|
1908
|
+
* (`storefront`, `storefront-staging`), so pointing at an environment means
|
|
1909
|
+
* naming that app, exactly as a Wrangler environment means naming its own
|
|
1910
|
+
* Worker.
|
|
1911
|
+
*
|
|
1912
|
+
* Values land **staged**: Fly injects secrets when a Machine boots, so already
|
|
1913
|
+
* running Machines keep what they started with until the app is deployed or its
|
|
1914
|
+
* Machines are updated (`fly secrets deploy -a <app>`), while Machines created
|
|
1915
|
+
* after the push get them straight away. The connector deliberately restarts
|
|
1916
|
+
* nothing — see the note in `apps/api/src/lib/sync/connectors/fly.ts`.
|
|
1917
|
+
*/
|
|
1918
|
+
const flyDestinationSchema = z.object({
|
|
1919
|
+
provider: z.literal("fly"),
|
|
1920
|
+
/** Fly app name, as `fly apps list` prints it. */
|
|
1921
|
+
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)")
|
|
1922
|
+
});
|
|
1923
|
+
/**
|
|
1924
|
+
* Where inside Northflank a binding writes: one **secret group** in one
|
|
1925
|
+
* project.
|
|
1926
|
+
*
|
|
1927
|
+
* A secret group is Northflank's unit of injection — services and jobs in the
|
|
1928
|
+
* project inherit its variables, subject to the group's own restrictions and
|
|
1929
|
+
* priority. Those settings belong to the operator, not to seekrit: a binding
|
|
1930
|
+
* names an existing group and only ever writes its `variables` map, so
|
|
1931
|
+
* restrictions, priority, secret type, and any secret *files* stay as they were
|
|
1932
|
+
* configured.
|
|
1933
|
+
*
|
|
1934
|
+
* There is no environment field. Northflank has no per-group environment axis —
|
|
1935
|
+
* separate environments are separate projects (or separate groups restricted to
|
|
1936
|
+
* a stage), so the binding's seekrit environment maps to a group, one to one.
|
|
1937
|
+
*/
|
|
1938
|
+
const northflankDestinationSchema = z.object({
|
|
1939
|
+
provider: z.literal("northflank"),
|
|
1940
|
+
/** Project id — the slug in the project URL (`default-project`). */
|
|
1941
|
+
projectId: northflankIdSchema,
|
|
1942
|
+
/** Secret group id — the slug in the group's URL (`example-secret-group`). */
|
|
1943
|
+
secretGroupId: northflankIdSchema
|
|
1944
|
+
});
|
|
1870
1945
|
const syncDestinationSchema = z.discriminatedUnion("provider", [
|
|
1871
1946
|
vercelDestinationSchema,
|
|
1872
1947
|
cloudflareWorkersDestinationSchema,
|
|
@@ -1875,7 +1950,9 @@ const syncDestinationSchema = z.discriminatedUnion("provider", [
|
|
|
1875
1950
|
railwayDestinationSchema,
|
|
1876
1951
|
awsSecretsManagerDestinationSchema,
|
|
1877
1952
|
awsParameterStoreDestinationSchema,
|
|
1878
|
-
renderDestinationSchema
|
|
1953
|
+
renderDestinationSchema,
|
|
1954
|
+
flyDestinationSchema,
|
|
1955
|
+
northflankDestinationSchema
|
|
1879
1956
|
]);
|
|
1880
1957
|
/**
|
|
1881
1958
|
* How seekrit secret names become destination key names. Applied in order:
|
|
@@ -3080,7 +3157,7 @@ function isCliSessionToken(value) {
|
|
|
3080
3157
|
}
|
|
3081
3158
|
//#endregion
|
|
3082
3159
|
//#region package.json
|
|
3083
|
-
var version = "0.
|
|
3160
|
+
var version = "0.38.0";
|
|
3084
3161
|
//#endregion
|
|
3085
3162
|
//#region ../../packages/api-client/src/index.ts
|
|
3086
3163
|
var SeekritApiError = class extends Error {
|
|
@@ -6908,6 +6985,28 @@ function assertMember(value, allowed, flag, fallback) {
|
|
|
6908
6985
|
if (!allowed.includes(value)) fail(`unknown ${flag} "${value}" — one of: ${allowed.join(", ")}`);
|
|
6909
6986
|
return value;
|
|
6910
6987
|
}
|
|
6988
|
+
/**
|
|
6989
|
+
* Fly app names are DNS labels, and the two habitual slips are pasting the
|
|
6990
|
+
* hostname (`storefront.fly.dev`) or a name with capitals in it. Both are a 404
|
|
6991
|
+
* from Fly much later, so they are caught here with a message that says which.
|
|
6992
|
+
*/
|
|
6993
|
+
function assertFlyApp(value) {
|
|
6994
|
+
if (!value) fail("--fly-app is required for fly (the app name, e.g. storefront-production)");
|
|
6995
|
+
const appName = value.trim();
|
|
6996
|
+
if (/\.(fly\.dev|internal)$/.test(appName)) fail(`--fly-app takes the app name, not its hostname — try "${appName.split(".")[0]}"`);
|
|
6997
|
+
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)`);
|
|
6998
|
+
return appName;
|
|
6999
|
+
}
|
|
7000
|
+
/**
|
|
7001
|
+
* Northflank ids are slugs from the resource's URL, so the slip to catch is the
|
|
7002
|
+
* *display name* — "App Secrets" where "app-secrets" belongs.
|
|
7003
|
+
*/
|
|
7004
|
+
function assertNorthflankId(value, flag) {
|
|
7005
|
+
if (!value) fail(`${flag} is required for northflank (the slug from its URL)`);
|
|
7006
|
+
const id = value.trim();
|
|
7007
|
+
if (!/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/.test(id)) fail(`${flag} should be a Northflank ID like "app-secrets", not "${id}"`);
|
|
7008
|
+
return id;
|
|
7009
|
+
}
|
|
6911
7010
|
function assertProvider(value) {
|
|
6912
7011
|
if (!SYNC_PROVIDER_KINDS.includes(value)) fail(`unknown provider "${value}" — one of: ${SYNC_PROVIDER_KINDS.join(", ")}`);
|
|
6913
7012
|
return value;
|
|
@@ -6945,6 +7044,8 @@ function buildConfig(provider, options) {
|
|
|
6945
7044
|
accessKeyId: options.accessKeyId
|
|
6946
7045
|
};
|
|
6947
7046
|
case "render": return { provider: "render" };
|
|
7047
|
+
case "fly": return { provider: "fly" };
|
|
7048
|
+
case "northflank": return { provider: "northflank" };
|
|
6948
7049
|
}
|
|
6949
7050
|
}
|
|
6950
7051
|
/** Where inside the platform a binding writes. */
|
|
@@ -7034,6 +7135,15 @@ function buildDestination(provider, options) {
|
|
|
7034
7135
|
};
|
|
7035
7136
|
return fail("--service (srv-…, or crn-… for a cron job) or --env-group (evg-…) is required for render");
|
|
7036
7137
|
}
|
|
7138
|
+
case "fly": return {
|
|
7139
|
+
provider: "fly",
|
|
7140
|
+
appName: assertFlyApp(options.flyApp)
|
|
7141
|
+
};
|
|
7142
|
+
case "northflank": return {
|
|
7143
|
+
provider: "northflank",
|
|
7144
|
+
projectId: assertNorthflankId(options.project, "--project"),
|
|
7145
|
+
secretGroupId: assertNorthflankId(options.secretGroup, "--secret-group")
|
|
7146
|
+
};
|
|
7037
7147
|
}
|
|
7038
7148
|
}
|
|
7039
7149
|
/** One-line description of a destination, for list output. */
|
|
@@ -7047,14 +7157,19 @@ function describeDestination(destination) {
|
|
|
7047
7157
|
case "aws-secrets-manager": return destination.layout === "json-bundle" ? `${destination.secretName} (json bundle)` : `${destination.pathPrefix ?? ""}* (secret per name)`;
|
|
7048
7158
|
case "aws-parameter-store": return `${destination.path}* (${destination.type})`;
|
|
7049
7159
|
case "render": return destination.kind === "service" ? destination.serviceId : `env group ${destination.envGroupId}`;
|
|
7160
|
+
case "fly": return destination.appName;
|
|
7161
|
+
case "northflank": return `${destination.projectId} / ${destination.secretGroupId}`;
|
|
7050
7162
|
}
|
|
7051
7163
|
}
|
|
7052
7164
|
/**
|
|
7053
7165
|
* Destination flags, shared by `verify` and `enable` so the two can never drift
|
|
7054
7166
|
* into accepting different ways of naming the same destination.
|
|
7167
|
+
*
|
|
7168
|
+
* Fly's is `--fly-app`, not `--app`: `--app` already names the seekrit
|
|
7169
|
+
* application whose environment the binding reads from.
|
|
7055
7170
|
*/
|
|
7056
7171
|
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-…)");
|
|
7172
|
+
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)");
|
|
7058
7173
|
}
|
|
7059
7174
|
/** Find a connection by id or name — nobody keeps `syc_…` ids in their head. */
|
|
7060
7175
|
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.38.0",
|
|
4
4
|
"description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"tsdown": "^0.22.3",
|
|
29
29
|
"vitest": "^4.1.9",
|
|
30
30
|
"@seekrit/api-client": "0.0.1",
|
|
31
|
-
"@seekrit/
|
|
32
|
-
"@seekrit/
|
|
31
|
+
"@seekrit/core": "0.0.1",
|
|
32
|
+
"@seekrit/crypto": "0.0.1"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsdown",
|