@seekrit/cli 0.36.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 +186 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1520,7 +1520,10 @@ const SYNC_PROVIDER_KINDS = [
|
|
|
1520
1520
|
"cloudflare-secrets-store",
|
|
1521
1521
|
"railway",
|
|
1522
1522
|
"aws-secrets-manager",
|
|
1523
|
-
"aws-parameter-store"
|
|
1523
|
+
"aws-parameter-store",
|
|
1524
|
+
"render",
|
|
1525
|
+
"fly",
|
|
1526
|
+
"northflank"
|
|
1524
1527
|
];
|
|
1525
1528
|
z.enum(SYNC_PROVIDER_KINDS);
|
|
1526
1529
|
/**
|
|
@@ -1646,6 +1649,45 @@ const awsParameterStoreConnectionConfigSchema = z.object({
|
|
|
1646
1649
|
region: awsRegionSchema,
|
|
1647
1650
|
accessKeyId: awsAccessKeyIdSchema
|
|
1648
1651
|
});
|
|
1652
|
+
/**
|
|
1653
|
+
* Render account scope — deliberately empty.
|
|
1654
|
+
*
|
|
1655
|
+
* Like Railway's, and unlike Vercel (which 403s team-owned resources without
|
|
1656
|
+
* `teamId`) or Cloudflare (whose every endpoint is account-scoped): a Render
|
|
1657
|
+
* API key is issued to a user, and every endpoint seekrit calls addresses its
|
|
1658
|
+
* resource by id — `srv-…`, `crn-…`, `evg-…`. There is nothing to scope, so
|
|
1659
|
+
* nothing is stored. The connection's `name` is what tells an operator which Render
|
|
1660
|
+
* workspace it belongs to.
|
|
1661
|
+
*/
|
|
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") });
|
|
1649
1691
|
const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
|
|
1650
1692
|
vercelConnectionConfigSchema,
|
|
1651
1693
|
cloudflareWorkersConnectionConfigSchema,
|
|
@@ -1653,7 +1695,10 @@ const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
|
|
|
1653
1695
|
cloudflareSecretsStoreConnectionConfigSchema,
|
|
1654
1696
|
railwayConnectionConfigSchema,
|
|
1655
1697
|
awsSecretsManagerConnectionConfigSchema,
|
|
1656
|
-
awsParameterStoreConnectionConfigSchema
|
|
1698
|
+
awsParameterStoreConnectionConfigSchema,
|
|
1699
|
+
renderConnectionConfigSchema,
|
|
1700
|
+
flyConnectionConfigSchema,
|
|
1701
|
+
northflankConnectionConfigSchema
|
|
1657
1702
|
]);
|
|
1658
1703
|
/** Vercel's three deployment targets. A binding writes to one or more. */
|
|
1659
1704
|
const VERCEL_TARGETS = [
|
|
@@ -1820,6 +1865,83 @@ const awsParameterStoreDestinationSchema = z.object({
|
|
|
1820
1865
|
tier: z.enum(AWS_PARAMETER_TIERS).default("Standard"),
|
|
1821
1866
|
kmsKeyId: awsKmsKeyIdSchema.optional()
|
|
1822
1867
|
});
|
|
1868
|
+
/**
|
|
1869
|
+
* Render resource ids are `<prefix>-<slug>`, and the two prefixes below are the
|
|
1870
|
+
* documented ones: `srv-` for every service type, `crn-` for cron jobs, `evg-`
|
|
1871
|
+
* for an environment group.
|
|
1872
|
+
*
|
|
1873
|
+
* The patterns reject the *other* kind's prefix rather than requiring their own.
|
|
1874
|
+
* The mistake worth catching is pasting an env-group id into the service field
|
|
1875
|
+
* (or the reverse) — which is otherwise a 404 hours later inside an alarm, with
|
|
1876
|
+
* nobody watching. Requiring the positive prefix would also reject a valid id
|
|
1877
|
+
* the day Render introduces a new resource prefix, which is not our call to
|
|
1878
|
+
* make.
|
|
1879
|
+
*/
|
|
1880
|
+
const renderServiceIdSchema = z.string().trim().regex(/^(?!evg-)[A-Za-z0-9_-]{1,64}$/, "must be a Render service ID (`srv-…` or `crn-…`), not an environment group");
|
|
1881
|
+
const renderEnvGroupIdSchema = z.string().trim().regex(/^(?!srv-|crn-)[A-Za-z0-9_-]{1,64}$/, "must be a Render environment group ID (`evg-…`), not a service");
|
|
1882
|
+
/** Environment variables set directly on one service. */
|
|
1883
|
+
const renderServiceDestinationSchema = z.object({
|
|
1884
|
+
provider: z.literal("render"),
|
|
1885
|
+
kind: z.literal("service"),
|
|
1886
|
+
/** Service id (`srv-…`, or `crn-…` for a cron job), from its dashboard URL. */
|
|
1887
|
+
serviceId: renderServiceIdSchema
|
|
1888
|
+
});
|
|
1889
|
+
/**
|
|
1890
|
+
* Environment variables in a shared environment group. Every service linked to
|
|
1891
|
+
* the group sees them, which is the point — and the reason a group binding is
|
|
1892
|
+
* worth thinking about twice: its blast radius is the link list, not one
|
|
1893
|
+
* service.
|
|
1894
|
+
*/
|
|
1895
|
+
const renderEnvGroupDestinationSchema = z.object({
|
|
1896
|
+
provider: z.literal("render"),
|
|
1897
|
+
kind: z.literal("env-group"),
|
|
1898
|
+
/** Environment group id (`evg-…`), from its dashboard URL. */
|
|
1899
|
+
envGroupId: renderEnvGroupIdSchema
|
|
1900
|
+
});
|
|
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
|
+
});
|
|
1823
1945
|
const syncDestinationSchema = z.discriminatedUnion("provider", [
|
|
1824
1946
|
vercelDestinationSchema,
|
|
1825
1947
|
cloudflareWorkersDestinationSchema,
|
|
@@ -1827,7 +1949,10 @@ const syncDestinationSchema = z.discriminatedUnion("provider", [
|
|
|
1827
1949
|
cloudflareSecretsStoreDestinationSchema,
|
|
1828
1950
|
railwayDestinationSchema,
|
|
1829
1951
|
awsSecretsManagerDestinationSchema,
|
|
1830
|
-
awsParameterStoreDestinationSchema
|
|
1952
|
+
awsParameterStoreDestinationSchema,
|
|
1953
|
+
renderDestinationSchema,
|
|
1954
|
+
flyDestinationSchema,
|
|
1955
|
+
northflankDestinationSchema
|
|
1831
1956
|
]);
|
|
1832
1957
|
/**
|
|
1833
1958
|
* How seekrit secret names become destination key names. Applied in order:
|
|
@@ -3032,7 +3157,7 @@ function isCliSessionToken(value) {
|
|
|
3032
3157
|
}
|
|
3033
3158
|
//#endregion
|
|
3034
3159
|
//#region package.json
|
|
3035
|
-
var version = "0.
|
|
3160
|
+
var version = "0.38.0";
|
|
3036
3161
|
//#endregion
|
|
3037
3162
|
//#region ../../packages/api-client/src/index.ts
|
|
3038
3163
|
var SeekritApiError = class extends Error {
|
|
@@ -6860,6 +6985,28 @@ function assertMember(value, allowed, flag, fallback) {
|
|
|
6860
6985
|
if (!allowed.includes(value)) fail(`unknown ${flag} "${value}" — one of: ${allowed.join(", ")}`);
|
|
6861
6986
|
return value;
|
|
6862
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
|
+
}
|
|
6863
7010
|
function assertProvider(value) {
|
|
6864
7011
|
if (!SYNC_PROVIDER_KINDS.includes(value)) fail(`unknown provider "${value}" — one of: ${SYNC_PROVIDER_KINDS.join(", ")}`);
|
|
6865
7012
|
return value;
|
|
@@ -6896,6 +7043,9 @@ function buildConfig(provider, options) {
|
|
|
6896
7043
|
region: options.region,
|
|
6897
7044
|
accessKeyId: options.accessKeyId
|
|
6898
7045
|
};
|
|
7046
|
+
case "render": return { provider: "render" };
|
|
7047
|
+
case "fly": return { provider: "fly" };
|
|
7048
|
+
case "northflank": return { provider: "northflank" };
|
|
6899
7049
|
}
|
|
6900
7050
|
}
|
|
6901
7051
|
/** Where inside the platform a binding writes. */
|
|
@@ -6969,6 +7119,31 @@ function buildDestination(provider, options) {
|
|
|
6969
7119
|
tier: assertMember(options.tier, AWS_PARAMETER_TIERS, "--tier", "Standard"),
|
|
6970
7120
|
...options.kmsKeyId ? { kmsKeyId: options.kmsKeyId } : {}
|
|
6971
7121
|
};
|
|
7122
|
+
case "render": {
|
|
7123
|
+
const serviceId = options.service?.trim();
|
|
7124
|
+
const envGroupId = options.envGroup?.trim();
|
|
7125
|
+
if (serviceId && envGroupId) fail("pass --service or --env-group for render, not both — a binding writes to one");
|
|
7126
|
+
if (serviceId) return {
|
|
7127
|
+
provider: "render",
|
|
7128
|
+
kind: "service",
|
|
7129
|
+
serviceId
|
|
7130
|
+
};
|
|
7131
|
+
if (envGroupId) return {
|
|
7132
|
+
provider: "render",
|
|
7133
|
+
kind: "env-group",
|
|
7134
|
+
envGroupId
|
|
7135
|
+
};
|
|
7136
|
+
return fail("--service (srv-…, or crn-… for a cron job) or --env-group (evg-…) is required for render");
|
|
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
|
+
};
|
|
6972
7147
|
}
|
|
6973
7148
|
}
|
|
6974
7149
|
/** One-line description of a destination, for list output. */
|
|
@@ -6981,14 +7156,20 @@ function describeDestination(destination) {
|
|
|
6981
7156
|
case "railway": return `${destination.projectId} / ${destination.environmentId} (${destination.serviceId ? `service ${destination.serviceId}` : "shared"})`;
|
|
6982
7157
|
case "aws-secrets-manager": return destination.layout === "json-bundle" ? `${destination.secretName} (json bundle)` : `${destination.pathPrefix ?? ""}* (secret per name)`;
|
|
6983
7158
|
case "aws-parameter-store": return `${destination.path}* (${destination.type})`;
|
|
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}`;
|
|
6984
7162
|
}
|
|
6985
7163
|
}
|
|
6986
7164
|
/**
|
|
6987
7165
|
* Destination flags, shared by `verify` and `enable` so the two can never drift
|
|
6988
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.
|
|
6989
7170
|
*/
|
|
6990
7171
|
function destinationOptions(command) {
|
|
6991
|
-
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)").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");
|
|
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)");
|
|
6992
7173
|
}
|
|
6993
7174
|
/** Find a connection by id or name — nobody keeps `syc_…` ids in their head. */
|
|
6994
7175
|
async function resolveConnection(ctx, orgId, ref) {
|