@devdogsuga/devtools 0.1.20 → 0.1.22
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/bws/pace.d.ts +1 -3
- package/dist/bws/pace.js +1 -1
- package/dist/bws/token.js +1 -1
- package/dist/bws/vault.js +2 -3
- package/dist/cli.js +4 -2
- package/dist/commands.js +1 -1
- package/dist/cron/discovery.js +3 -3
- package/dist/db/new-migration.js +4 -1
- package/dist/db/seed-roles.js +3 -1
- package/dist/deploy/secrets-file.js +8 -9
- package/dist/deploy/write-env.js +3 -1
- package/dist/env/audit.js +3 -3
- package/dist/env/commands.js +2 -1
- package/dist/env/example.js +3 -1
- package/dist/env/selection.js +3 -1
- package/dist/environment-doctor.d.ts +3 -3
- package/dist/environment-doctor.js +37 -16
- package/dist/environment.js +3 -1
- package/dist/gen/hypno.js +1 -1
- package/dist/gh/client.js +2 -1
- package/dist/gh/rulesets/actors.js +2 -1
- package/dist/gh/rulesets/api.js +5 -2
- package/dist/gh/rulesets/desired.js +30 -6
- package/dist/gh/rulesets/diff.js +3 -1
- package/dist/gh/settings/api.js +11 -3
- package/dist/gh/settings/commands.js +15 -6
- package/dist/gh/settings/desired.js +5 -1
- package/dist/gh/settings/diff.js +10 -5
- package/dist/gh/settings/workflows.js +5 -1
- package/dist/images/commands.js +8 -6
- package/dist/images/render.js +5 -1
- package/dist/launch-ci.js +3 -1
- package/dist/launch.js +8 -6
- package/dist/moderation.js +5 -3
- package/dist/oauth/db.js +1 -1
- package/dist/oauth/device.js +12 -4
- package/dist/oauth/exchange.js +9 -3
- package/dist/oauth/loopback.js +11 -5
- package/dist/oauth/wizard.js +16 -5
- package/dist/persona.js +1 -1
- package/dist/planner/checks.js +1 -1
- package/dist/planner/commands.js +1 -1
- package/dist/planner/db.js +1 -1
- package/dist/repo/source.d.ts +1 -1
- package/dist/run/pick.js +1 -1
- package/dist/setup.js +1 -1
- package/dist/telemetry.js +3 -1
- package/dist/tier.js +5 -2
- package/dist/version.js +1 -3
- package/dist/workers.js +1 -3
- package/dist/workflows/commands.js +3 -2
- package/env.ts +5 -5
- package/package.json +10 -7
package/dist/bws/pace.d.ts
CHANGED
|
@@ -21,7 +21,5 @@
|
|
|
21
21
|
export type CallKind = "read" | "write";
|
|
22
22
|
export declare const WRITE_GAP_MS = 1100;
|
|
23
23
|
export declare const READ_GAP_MS = 350;
|
|
24
|
-
export
|
|
25
|
-
(kind: CallKind): Promise<void>;
|
|
26
|
-
}
|
|
24
|
+
export type Pacer = (kind: CallKind) => Promise<void>;
|
|
27
25
|
export declare function makePacer(now?: () => number, sleep?: (ms: number) => Promise<void>): Pacer;
|
package/dist/bws/pace.js
CHANGED
|
@@ -37,7 +37,7 @@ export function makePacer(now = Date.now, sleep = wait) {
|
|
|
37
37
|
});
|
|
38
38
|
// The chain never rejects: a pacer that fails closed would wedge every
|
|
39
39
|
// later call behind a rejection that had nothing to do with them.
|
|
40
|
-
chain = turn.catch(() =>
|
|
40
|
+
chain = turn.catch(() => undefined);
|
|
41
41
|
return turn;
|
|
42
42
|
};
|
|
43
43
|
}
|
package/dist/bws/token.js
CHANGED
|
@@ -18,7 +18,7 @@ import { unwrap } from "../ui.js";
|
|
|
18
18
|
export class NoAccessTokenError extends Error {
|
|
19
19
|
}
|
|
20
20
|
export async function resolveToken(sources) {
|
|
21
|
-
const announce = sources.onSource ?? (() =>
|
|
21
|
+
const announce = sources.onSource ?? (() => undefined);
|
|
22
22
|
// Explicit beats ambient. Someone who passes the flag while an environment
|
|
23
23
|
// variable is set is overriding on purpose, and silently ignoring them would
|
|
24
24
|
// point the command at the account they were trying to avoid.
|
package/dist/bws/vault.js
CHANGED
|
@@ -40,9 +40,8 @@ export async function vaultStatus() {
|
|
|
40
40
|
// `--response` wraps the payload; the bare form is also accepted, so read
|
|
41
41
|
// whichever shape came back rather than depending on one.
|
|
42
42
|
const parsed = JSON.parse(stdout);
|
|
43
|
-
const status = ("status" in parsed
|
|
44
|
-
("data" in parsed
|
|
45
|
-
undefined;
|
|
43
|
+
const status = ("status" in parsed ? parsed.status : undefined) ??
|
|
44
|
+
("data" in parsed ? parsed.data?.template?.status : undefined);
|
|
46
45
|
if (status === "unlocked")
|
|
47
46
|
return "unlocked";
|
|
48
47
|
if (status === "locked")
|
package/dist/cli.js
CHANGED
|
@@ -45,7 +45,7 @@ import { loadEnv } from "./repo/peers.js";
|
|
|
45
45
|
import { setExplicitAccessToken } from "./bws/client.js";
|
|
46
46
|
import { positionals } from "./args.js";
|
|
47
47
|
import { resolveVaultTarget } from "./pick.js";
|
|
48
|
-
import { bail, errorMessage, explain, explainError, renderChecks, unwrap } from "./ui.js";
|
|
48
|
+
import { bail, errorMessage, explain, explainError, renderChecks, unwrap, } from "./ui.js";
|
|
49
49
|
import { helpPath, renderHelp } from "./help.js";
|
|
50
50
|
import { subcommandList, subcommandNames } from "./commands.js";
|
|
51
51
|
import { bareGroupStartPath, runMenu } from "./menu.js";
|
|
@@ -597,7 +597,9 @@ async function runDbCommand(rest) {
|
|
|
597
597
|
return;
|
|
598
598
|
}
|
|
599
599
|
if (sub === "types") {
|
|
600
|
-
const connection = await resolveDbConnection({
|
|
600
|
+
const connection = await resolveDbConnection({
|
|
601
|
+
label: "devtools db types",
|
|
602
|
+
});
|
|
601
603
|
if (!connection) {
|
|
602
604
|
process.exitCode = 1;
|
|
603
605
|
return;
|
package/dist/commands.js
CHANGED
|
@@ -677,7 +677,7 @@ const DECLARED_GROUPS = [
|
|
|
677
677
|
{
|
|
678
678
|
flag: "--base-url",
|
|
679
679
|
value: "<url>",
|
|
680
|
-
summary:
|
|
680
|
+
summary: 'DevDogs API URL for "Paste credentials instead". Defaults to https://api.devdogsuga.org.',
|
|
681
681
|
},
|
|
682
682
|
{
|
|
683
683
|
flag: "--platform-url",
|
package/dist/cron/discovery.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { pathToFileURL } from "node:url";
|
|
11
|
-
import { parseConfigFileTextToJson } from "typescript";
|
|
11
|
+
import { flattenDiagnosticMessageText, parseConfigFileTextToJson, } from "typescript";
|
|
12
12
|
import { findRepoRoot } from "../repo/root.js";
|
|
13
13
|
import { CronRoutes, WorkflowCrons, } from "./schema.js";
|
|
14
14
|
export const CRON_TIERS = ["development", "staging", "production"];
|
|
@@ -19,7 +19,7 @@ export function isCronTier(value) {
|
|
|
19
19
|
export function parseWrangler(path) {
|
|
20
20
|
const result = parseConfigFileTextToJson(path, readFileSync(path, "utf8"));
|
|
21
21
|
if (result.error) {
|
|
22
|
-
throw new Error(`${path}: ${result.error.messageText}`);
|
|
22
|
+
throw new Error(`${path}: ${flattenDiagnosticMessageText(result.error.messageText, "\n")}`);
|
|
23
23
|
}
|
|
24
24
|
return result.config;
|
|
25
25
|
}
|
|
@@ -57,7 +57,7 @@ export async function discoverCronMaps() {
|
|
|
57
57
|
const scheduledPath = join(findRepoRoot(), "apps", app, "cloudflare", "scheduled.ts");
|
|
58
58
|
if (!existsSync(scheduledPath))
|
|
59
59
|
continue;
|
|
60
|
-
const mod = await import(pathToFileURL(scheduledPath).href);
|
|
60
|
+
const mod = (await import(pathToFileURL(scheduledPath).href));
|
|
61
61
|
const parsed = CronRoutes.safeParse(mod.CRON_ROUTES);
|
|
62
62
|
if (!parsed.success) {
|
|
63
63
|
throw new Error(`${scheduledPath}: CRON_ROUTES does not match the required shape.\n` +
|
package/dist/db/new-migration.js
CHANGED
|
@@ -42,7 +42,10 @@ export function migrationName(appSlug, description) {
|
|
|
42
42
|
async function pickApp() {
|
|
43
43
|
return unwrap(await select({
|
|
44
44
|
message: "Whose schema is this migration for?",
|
|
45
|
-
options: Object.keys(APP_SCHEMAS).map((value) => ({
|
|
45
|
+
options: Object.keys(APP_SCHEMAS).map((value) => ({
|
|
46
|
+
value,
|
|
47
|
+
label: value,
|
|
48
|
+
})),
|
|
46
49
|
}));
|
|
47
50
|
}
|
|
48
51
|
export async function runNewMigration(appArg, descriptionArg) {
|
package/dist/db/seed-roles.js
CHANGED
|
@@ -13,7 +13,9 @@ import { supabase } from "./run.js";
|
|
|
13
13
|
// top-level `const`, which broke `--help`/`setup` outside a repo — see
|
|
14
14
|
// `workers.ts`'s header for the general pattern this follows).
|
|
15
15
|
function seedFiles() {
|
|
16
|
-
return [
|
|
16
|
+
return [
|
|
17
|
+
join(findRepoRoot(), "supabase", "seed", "production", "01_roles.sql"),
|
|
18
|
+
];
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
21
|
* Always `--db-url` — the session's own connection string, never the
|
|
@@ -83,15 +83,14 @@ export async function runDeploySecretsFile(options) {
|
|
|
83
83
|
if (!declaredBy(entries, app))
|
|
84
84
|
continue;
|
|
85
85
|
const meta = entries[0].meta;
|
|
86
|
-
// Public server keys ship alongside the secrets
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
// because a Worker secret is a remote copy, the thing they forbid.
|
|
86
|
+
// Public server keys ship alongside the secrets because a value that was
|
|
87
|
+
// only in the composed .env file at build time is absent from the deployed
|
|
88
|
+
// Worker's runtime environment. The first staging deploy proved it: the
|
|
89
|
+
// schedule-builder Worker booted without API_URL, REST_URL,
|
|
90
|
+
// PUBLISHABLE_KEY, STORAGE_S3_URL or S3_PROTOCOL_REGION and answered 500
|
|
91
|
+
// on every route. Client (NEXT_PUBLIC) keys are excluded by `declaredBy`
|
|
92
|
+
// (inlined at build); never-store keys stay excluded because a Worker
|
|
93
|
+
// secret is a remote copy, the thing they forbid.
|
|
95
94
|
if (meta.secrecy === "secret") {
|
|
96
95
|
if (!storable.has(key)) {
|
|
97
96
|
minted.push(key);
|
package/dist/deploy/write-env.js
CHANGED
|
@@ -237,7 +237,9 @@ function compose(options, env) {
|
|
|
237
237
|
}
|
|
238
238
|
const ghSecrets = readContext("DEPLOY_GITHUB_SECRETS", env);
|
|
239
239
|
const ghVars = readContext("DEPLOY_GITHUB_VARS", env);
|
|
240
|
-
const smuggled = getEnvSync()
|
|
240
|
+
const smuggled = getEnvSync()
|
|
241
|
+
.neverStoreKeys()
|
|
242
|
+
.filter((k) => k in ghSecrets || k in ghVars);
|
|
241
243
|
if (smuggled.length > 0) {
|
|
242
244
|
throw new DeployError(`${smuggled.join(", ")} must never be a GitHub secret or variable.`, [
|
|
243
245
|
"Delete it from the repository and the environment, then rotate it.",
|
package/dist/env/audit.js
CHANGED
|
@@ -136,7 +136,7 @@ export function audit(input) {
|
|
|
136
136
|
// variable store is an orphan from a rename, reported as one below.
|
|
137
137
|
// Calling it a leaked secret would be a guess, and a loud wrong one. With
|
|
138
138
|
// no declared set there is no way to tell the two apart, so neither fires.
|
|
139
|
-
if (
|
|
139
|
+
if (!input.declared?.has(copy.name))
|
|
140
140
|
continue;
|
|
141
141
|
findings.push({
|
|
142
142
|
key: copy.name,
|
|
@@ -174,7 +174,7 @@ export function audit(input) {
|
|
|
174
174
|
"exactly like this report. Run `gh variable list` by hand",
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
|
-
const repositoryNames = repository
|
|
177
|
+
const repositoryNames = repository?.readable ? repository.names : [];
|
|
178
178
|
for (const name of repositoryNames) {
|
|
179
179
|
// Membership in `variables` IS the declaration test for this branch, since
|
|
180
180
|
// the set comes from the registry, which is why it does not also consult
|
|
@@ -213,7 +213,7 @@ export function audit(input) {
|
|
|
213
213
|
// variable, and calling that a leaked secret would be a loud guess.
|
|
214
214
|
if (ignore.has(name))
|
|
215
215
|
continue;
|
|
216
|
-
if (
|
|
216
|
+
if (!input.declared?.has(name))
|
|
217
217
|
continue;
|
|
218
218
|
findings.push({
|
|
219
219
|
key: name,
|
package/dist/env/commands.js
CHANGED
|
@@ -491,12 +491,13 @@ export async function runEnvAudit(options) {
|
|
|
491
491
|
};
|
|
492
492
|
}
|
|
493
493
|
catch (err) {
|
|
494
|
+
const firstLine = errorMessage(err).split("\n")[0]?.trim() ?? "";
|
|
494
495
|
repositoryVariables = {
|
|
495
496
|
readable: false,
|
|
496
497
|
// The FIRST line only. `describe()` in the gh client returns a paragraph
|
|
497
498
|
// of guidance, and a finding is one line. The rest is reproducible by
|
|
498
499
|
// running the command the finding names.
|
|
499
|
-
reason:
|
|
500
|
+
reason: firstLine || "`gh` failed",
|
|
500
501
|
};
|
|
501
502
|
}
|
|
502
503
|
const { secrets: cloudflare, unreadable } = await listWorkerSecrets(target);
|
package/dist/env/example.js
CHANGED
|
@@ -190,7 +190,9 @@ function derivedValue(meta, routed) {
|
|
|
190
190
|
const derivation = getEnvSync().derivationOf(meta);
|
|
191
191
|
if (derivation === null)
|
|
192
192
|
return "";
|
|
193
|
-
return getEnvSync()
|
|
193
|
+
return getEnvSync()
|
|
194
|
+
.envReferences(derivation)
|
|
195
|
+
.every((ref) => routed.has(ref))
|
|
194
196
|
? derivation
|
|
195
197
|
: "";
|
|
196
198
|
}
|
package/dist/env/selection.js
CHANGED
|
@@ -53,7 +53,9 @@ export function pushableVariables() {
|
|
|
53
53
|
export function ignoredFor(target) {
|
|
54
54
|
assertRegistryLoaded();
|
|
55
55
|
const pushable = pushableVariables();
|
|
56
|
-
const skip = new Set(getEnvSync()
|
|
56
|
+
const skip = new Set(getEnvSync()
|
|
57
|
+
.neverSecretKeys()
|
|
58
|
+
.filter((key) => !pushable.has(key)));
|
|
57
59
|
for (const key of getEnvSync().mintedKeys())
|
|
58
60
|
skip.add(key);
|
|
59
61
|
if (target !== "production") {
|
|
@@ -12,9 +12,9 @@ export interface DoctorCheck {
|
|
|
12
12
|
* pins 24, which is what a `fnm use` picks up; a machine below the floor is
|
|
13
13
|
* a WARN, not an error — plenty of contributors' shells still resolve an
|
|
14
14
|
* old global `node` first. */
|
|
15
|
-
export declare function checkNodeVersion(nodeVersion: string
|
|
16
|
-
export declare function checkFnmHookPresent(shellProfile: string | null
|
|
17
|
-
export declare function checkPnpmVersion(pnpmVersion: string | null, pinned: string
|
|
15
|
+
export declare function checkNodeVersion(nodeVersion: string): DoctorCheck;
|
|
16
|
+
export declare function checkFnmHookPresent(shellProfile: string | null): DoctorCheck;
|
|
17
|
+
export declare function checkPnpmVersion(pnpmVersion: string | null, pinned: string): DoctorCheck;
|
|
18
18
|
/** `git config core.autocrlf` — `input` or `false` is correct on every
|
|
19
19
|
* platform this repo documents (WSL2, native Windows, macOS, Linux); `true`
|
|
20
20
|
* rewrites LF to CRLF on checkout and breaks anything that shells out to a
|
|
@@ -26,7 +26,11 @@ import { log, note } from "@clack/prompts";
|
|
|
26
26
|
import { parse as parseDotenv } from "dotenv";
|
|
27
27
|
import { discoverRepoRoot } from "./repo/root.js";
|
|
28
28
|
import { validateSessionPoolerUrl } from "./db/pooler.js";
|
|
29
|
-
const TROUBLESHOOTING_APPS = [
|
|
29
|
+
const TROUBLESHOOTING_APPS = [
|
|
30
|
+
"schedule-builder",
|
|
31
|
+
"study-group-finder",
|
|
32
|
+
"platform",
|
|
33
|
+
];
|
|
30
34
|
function faqUrl(app, id) {
|
|
31
35
|
return `https://devdogsuga.org/docs/${app}/getting-started/troubleshooting#${id}`;
|
|
32
36
|
}
|
|
@@ -35,7 +39,7 @@ function faqUrl(app, id) {
|
|
|
35
39
|
* pins 24, which is what a `fnm use` picks up; a machine below the floor is
|
|
36
40
|
* a WARN, not an error — plenty of contributors' shells still resolve an
|
|
37
41
|
* old global `node` first. */
|
|
38
|
-
export function checkNodeVersion(nodeVersion
|
|
42
|
+
export function checkNodeVersion(nodeVersion) {
|
|
39
43
|
const [major = 0, minor = 0] = nodeVersion.split(".").map(Number);
|
|
40
44
|
const ok = major > 22 || (major === 22 && minor >= 12);
|
|
41
45
|
return {
|
|
@@ -50,8 +54,9 @@ export function checkNodeVersion(nodeVersion, app) {
|
|
|
50
54
|
faqId: ok ? undefined : "node-version",
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
|
-
export function checkFnmHookPresent(shellProfile
|
|
54
|
-
const present =
|
|
57
|
+
export function checkFnmHookPresent(shellProfile) {
|
|
58
|
+
const present = shellProfile !== null &&
|
|
59
|
+
(shellProfile.includes("fnm env") || shellProfile.includes("fnm_env"));
|
|
55
60
|
return {
|
|
56
61
|
id: "fnm-not-found",
|
|
57
62
|
status: present ? "ok" : "warn",
|
|
@@ -64,13 +69,13 @@ export function checkFnmHookPresent(shellProfile, app) {
|
|
|
64
69
|
faqId: present ? undefined : "fnm-not-found",
|
|
65
70
|
};
|
|
66
71
|
}
|
|
67
|
-
export function checkPnpmVersion(pnpmVersion, pinned
|
|
72
|
+
export function checkPnpmVersion(pnpmVersion, pinned) {
|
|
68
73
|
if (!pnpmVersion) {
|
|
69
74
|
return {
|
|
70
75
|
id: "pnpm-missing",
|
|
71
76
|
status: "warn",
|
|
72
77
|
summary: "pnpm is not on PATH",
|
|
73
|
-
fix:
|
|
78
|
+
fix: "`npm install -g pnpm` (never `corepack enable` — this repo does not use corepack).",
|
|
74
79
|
faqId: "pnpm-missing",
|
|
75
80
|
};
|
|
76
81
|
}
|
|
@@ -206,7 +211,12 @@ function readShellProfile() {
|
|
|
206
211
|
const home = process.env.HOME;
|
|
207
212
|
if (!home)
|
|
208
213
|
return null;
|
|
209
|
-
for (const file of [
|
|
214
|
+
for (const file of [
|
|
215
|
+
".bashrc",
|
|
216
|
+
".zshrc",
|
|
217
|
+
".config/fish/config.fish",
|
|
218
|
+
".bash_profile",
|
|
219
|
+
]) {
|
|
210
220
|
const path = join(home, file);
|
|
211
221
|
if (existsSync(path)) {
|
|
212
222
|
try {
|
|
@@ -234,13 +244,13 @@ export async function runEnvironmentDoctor(opts) {
|
|
|
234
244
|
const app = opts.app && TROUBLESHOOTING_APPS.includes(opts.app) ? opts.app : "platform";
|
|
235
245
|
const repoRoot = discoverRepoRoot();
|
|
236
246
|
const checks = [];
|
|
237
|
-
const nodeCheck = checkNodeVersion(process.versions.node
|
|
247
|
+
const nodeCheck = checkNodeVersion(process.versions.node);
|
|
238
248
|
checks.push(nodeCheck);
|
|
239
249
|
// Only worth raising when Node is wrong: nvm, Volta or a system Node that
|
|
240
250
|
// already satisfies the floor is fine, and flagging a missing fnm hook there
|
|
241
251
|
// is a false alarm.
|
|
242
252
|
if (nodeCheck.status !== "ok") {
|
|
243
|
-
checks.push(checkFnmHookPresent(readShellProfile()
|
|
253
|
+
checks.push(checkFnmHookPresent(readShellProfile()));
|
|
244
254
|
}
|
|
245
255
|
else {
|
|
246
256
|
const pinnedMajor = repoRoot ? readNvmrc(repoRoot)?.split(".")[0] : null;
|
|
@@ -258,14 +268,14 @@ export async function runEnvironmentDoctor(opts) {
|
|
|
258
268
|
const pnpmVersion = has("pnpm", ["--version"]);
|
|
259
269
|
const pin = repoRoot ? readPackageManagerPin(repoRoot) : null;
|
|
260
270
|
checks.push(pin
|
|
261
|
-
? checkPnpmVersion(pnpmVersion, pin
|
|
271
|
+
? checkPnpmVersion(pnpmVersion, pin)
|
|
262
272
|
: {
|
|
263
273
|
id: "pnpm-version",
|
|
264
274
|
status: pnpmVersion ? "ok" : "warn",
|
|
265
275
|
summary: pnpmVersion ? `pnpm ${pnpmVersion}` : "pnpm is not on PATH",
|
|
266
276
|
fix: pnpmVersion
|
|
267
277
|
? undefined
|
|
268
|
-
:
|
|
278
|
+
: "`npm install -g pnpm` (never `corepack enable`).",
|
|
269
279
|
faqId: pnpmVersion ? undefined : "pnpm-missing",
|
|
270
280
|
});
|
|
271
281
|
checks.push(checkAutocrlf(gitConfig("core.autocrlf")));
|
|
@@ -281,7 +291,9 @@ export async function runEnvironmentDoctor(opts) {
|
|
|
281
291
|
checks.push({
|
|
282
292
|
id: "docker-not-running",
|
|
283
293
|
status: dockerUp ? "ok" : "warn",
|
|
284
|
-
summary: dockerUp
|
|
294
|
+
summary: dockerUp
|
|
295
|
+
? "Docker is running"
|
|
296
|
+
: "Docker daemon is not answering",
|
|
285
297
|
fix: dockerUp
|
|
286
298
|
? undefined
|
|
287
299
|
: "Start Docker Desktop, or Docker Engine in WSL2. Never Colima.",
|
|
@@ -294,7 +306,9 @@ export async function runEnvironmentDoctor(opts) {
|
|
|
294
306
|
id: "flutter-missing",
|
|
295
307
|
status: flutter ? "ok" : "skip",
|
|
296
308
|
summary: flutter ? "Flutter is installed" : "Flutter is not installed",
|
|
297
|
-
fix: flutter
|
|
309
|
+
fix: flutter
|
|
310
|
+
? undefined
|
|
311
|
+
: "Install the Flutter SDK for study-group-finder.",
|
|
298
312
|
faqId: flutter ? undefined : "flutter-missing",
|
|
299
313
|
});
|
|
300
314
|
if (flutter) {
|
|
@@ -322,7 +336,9 @@ export async function runEnvironmentDoctor(opts) {
|
|
|
322
336
|
checks.push(checkEnvKeysPresent(env, ["API_URL", "PUBLISHABLE_KEY"]));
|
|
323
337
|
}
|
|
324
338
|
// ── Hosted checks — only when .env looks like a hosted project ───────────
|
|
325
|
-
const isHosted = Boolean(env.API_URL &&
|
|
339
|
+
const isHosted = Boolean(env.API_URL &&
|
|
340
|
+
!env.API_URL.includes("127.0.0.1") &&
|
|
341
|
+
!env.API_URL.includes("localhost"));
|
|
326
342
|
if (isHosted && env.API_URL) {
|
|
327
343
|
let reachable = false;
|
|
328
344
|
let probeStatus = null;
|
|
@@ -360,7 +376,10 @@ export async function runEnvironmentDoctor(opts) {
|
|
|
360
376
|
let keysValid = false;
|
|
361
377
|
try {
|
|
362
378
|
const res = await fetch(`${env.API_URL}/auth/v1/admin/users?page=1&per_page=1`, {
|
|
363
|
-
headers: {
|
|
379
|
+
headers: {
|
|
380
|
+
Authorization: `Bearer ${env.SECRET_KEY}`,
|
|
381
|
+
apikey: env.SECRET_KEY,
|
|
382
|
+
},
|
|
364
383
|
signal: AbortSignal.timeout(5000),
|
|
365
384
|
});
|
|
366
385
|
keysValid = res.status !== 401 && res.status !== 403;
|
|
@@ -371,7 +390,9 @@ export async function runEnvironmentDoctor(opts) {
|
|
|
371
390
|
checks.push({
|
|
372
391
|
id: "supabase-keys-invalid",
|
|
373
392
|
status: keysValid ? "ok" : "warn",
|
|
374
|
-
summary: keysValid
|
|
393
|
+
summary: keysValid
|
|
394
|
+
? "SECRET_KEY is accepted"
|
|
395
|
+
: "SECRET_KEY was rejected",
|
|
375
396
|
fix: keysValid
|
|
376
397
|
? undefined
|
|
377
398
|
: "Copy the service_role secret key again from Project Settings -> API.",
|
package/dist/environment.js
CHANGED
|
@@ -86,7 +86,9 @@ function run(file, args) {
|
|
|
86
86
|
* which reads the session's already-entered env at the moment it matters.
|
|
87
87
|
*/
|
|
88
88
|
export function probeEnvironment(execute = run) {
|
|
89
|
-
const envFile = existsSync(join(findRepoRoot(), ".env"))
|
|
89
|
+
const envFile = existsSync(join(findRepoRoot(), ".env"))
|
|
90
|
+
? "yes"
|
|
91
|
+
: "no";
|
|
90
92
|
// `docker info` rather than `docker version`: version answers from the
|
|
91
93
|
// client alone, so it reports success against a daemon that is not running.
|
|
92
94
|
const docker = execute("docker", ["info"]) === null ? "no" : "yes";
|
package/dist/gen/hypno.js
CHANGED
|
@@ -55,7 +55,7 @@ async function loadChromium() {
|
|
|
55
55
|
join(findRepoRoot(), "node_modules/.pnpm/playwright@1.62.1/node_modules/playwright"),
|
|
56
56
|
]) {
|
|
57
57
|
try {
|
|
58
|
-
const mod = await import(require_.resolve(spec));
|
|
58
|
+
const mod = (await import(require_.resolve(spec)));
|
|
59
59
|
const chromium = mod.chromium ?? mod.default?.chromium;
|
|
60
60
|
if (chromium)
|
|
61
61
|
return chromium;
|
package/dist/gh/client.js
CHANGED
|
@@ -107,7 +107,8 @@ function describe(err) {
|
|
|
107
107
|
"burst of pushes can trip the secondary limit — wait a minute and " +
|
|
108
108
|
"re-run; already-written values are simply overwritten with themselves.");
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
const message = e.message ?? "";
|
|
111
|
+
return stderr || message || "gh failed with no output.";
|
|
111
112
|
}
|
|
112
113
|
/** Environment secret names and when each last changed. Never values. */
|
|
113
114
|
export async function listSecrets(environment) {
|
|
@@ -17,7 +17,8 @@ const run = promisify(execFile);
|
|
|
17
17
|
function describe(err) {
|
|
18
18
|
const e = err;
|
|
19
19
|
const stderr = (e.stderr ?? "").trim();
|
|
20
|
-
|
|
20
|
+
const message = e.message ?? "";
|
|
21
|
+
return stderr || message || "gh failed with no output.";
|
|
21
22
|
}
|
|
22
23
|
/** `GET /orgs/{org}/teams/{slug}` -> the team's numeric id. */
|
|
23
24
|
export async function resolveTeamId(org, slug) {
|
package/dist/gh/rulesets/api.js
CHANGED
|
@@ -24,7 +24,8 @@ export class GhRulesetsError extends Error {
|
|
|
24
24
|
function describe(err) {
|
|
25
25
|
const e = err;
|
|
26
26
|
const stderr = (e.stderr ?? "").trim();
|
|
27
|
-
|
|
27
|
+
const message = e.message ?? "";
|
|
28
|
+
return stderr || message || "gh failed with no output.";
|
|
28
29
|
}
|
|
29
30
|
function path(r, suffix = "") {
|
|
30
31
|
return `repos/${r.owner}/${r.repo}/rulesets${suffix}`;
|
|
@@ -36,7 +37,9 @@ export async function listRulesets(r) {
|
|
|
36
37
|
maxBuffer: MAX_BUFFER,
|
|
37
38
|
shell: false,
|
|
38
39
|
});
|
|
39
|
-
return stdout.trim() === ""
|
|
40
|
+
return stdout.trim() === ""
|
|
41
|
+
? []
|
|
42
|
+
: JSON.parse(stdout);
|
|
40
43
|
}
|
|
41
44
|
catch (err) {
|
|
42
45
|
throw new GhRulesetsError(describe(err));
|
|
@@ -48,7 +48,11 @@ export function buildDesiredRulesets(actors) {
|
|
|
48
48
|
// side of a `main` push, so `always` (not `pull_request`-only) is the
|
|
49
49
|
// right bypass mode here.
|
|
50
50
|
bypass_actors: [
|
|
51
|
-
{
|
|
51
|
+
{
|
|
52
|
+
actor_id: actors.devopsTeamId,
|
|
53
|
+
actor_type: "Team",
|
|
54
|
+
bypass_mode: "always",
|
|
55
|
+
},
|
|
52
56
|
],
|
|
53
57
|
rules: [
|
|
54
58
|
{ type: "deletion" },
|
|
@@ -161,7 +165,11 @@ export function buildDesiredRulesets(actors) {
|
|
|
161
165
|
},
|
|
162
166
|
},
|
|
163
167
|
bypass_actors: [
|
|
164
|
-
{
|
|
168
|
+
{
|
|
169
|
+
actor_id: actors.devopsTeamId,
|
|
170
|
+
actor_type: "Team",
|
|
171
|
+
bypass_mode: "always",
|
|
172
|
+
},
|
|
165
173
|
// Renovate (TASK-299) creates and updates `renovate/*` branches for
|
|
166
174
|
// its dependency-update PRs — an ordinary bot workflow, not a hole,
|
|
167
175
|
// so it bypasses the same way the platform App bypasses `team/**`.
|
|
@@ -215,8 +223,16 @@ export function buildDesiredRulesets(actors) {
|
|
|
215
223
|
ref_name: { include: ["refs/heads/team/**"], exclude: [] },
|
|
216
224
|
},
|
|
217
225
|
bypass_actors: [
|
|
218
|
-
{
|
|
219
|
-
|
|
226
|
+
{
|
|
227
|
+
actor_id: actors.appId,
|
|
228
|
+
actor_type: "Integration",
|
|
229
|
+
bypass_mode: "always",
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
actor_id: actors.devopsTeamId,
|
|
233
|
+
actor_type: "Team",
|
|
234
|
+
bypass_mode: "always",
|
|
235
|
+
},
|
|
220
236
|
],
|
|
221
237
|
rules: [
|
|
222
238
|
{ type: "creation" },
|
|
@@ -238,8 +254,16 @@ export function buildDesiredRulesets(actors) {
|
|
|
238
254
|
ref_name: { include: ["refs/tags/**"], exclude: [] },
|
|
239
255
|
},
|
|
240
256
|
bypass_actors: [
|
|
241
|
-
{
|
|
242
|
-
|
|
257
|
+
{
|
|
258
|
+
actor_id: actors.adminsTeamId,
|
|
259
|
+
actor_type: "Team",
|
|
260
|
+
bypass_mode: "always",
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
actor_id: actors.devopsTeamId,
|
|
264
|
+
actor_type: "Team",
|
|
265
|
+
bypass_mode: "always",
|
|
266
|
+
},
|
|
243
267
|
],
|
|
244
268
|
rules: [{ type: "creation" }, { type: "update" }, { type: "deletion" }],
|
|
245
269
|
},
|
package/dist/gh/rulesets/diff.js
CHANGED
|
@@ -164,5 +164,7 @@ export function planRulesets(liveSummaries, liveDetails, actors, desiredRulesets
|
|
|
164
164
|
}
|
|
165
165
|
/** Whether a plan would write anything at all. */
|
|
166
166
|
export function planHasChanges(plan) {
|
|
167
|
-
return (plan.creates.length > 0 ||
|
|
167
|
+
return (plan.creates.length > 0 ||
|
|
168
|
+
plan.updates.length > 0 ||
|
|
169
|
+
plan.deletes.length > 0);
|
|
168
170
|
}
|
package/dist/gh/settings/api.js
CHANGED
|
@@ -22,7 +22,8 @@ export class GhSettingsError extends Error {
|
|
|
22
22
|
function describe(err) {
|
|
23
23
|
const e = err;
|
|
24
24
|
const stderr = (e.stderr ?? "").trim();
|
|
25
|
-
|
|
25
|
+
const message = e.message ?? "";
|
|
26
|
+
return stderr || message || "gh failed with no output.";
|
|
26
27
|
}
|
|
27
28
|
function repoPath(r, suffix = "") {
|
|
28
29
|
return `repos/${r.owner}/${r.repo}${suffix}`;
|
|
@@ -50,7 +51,10 @@ async function writeJson(method, apiPath, body) {
|
|
|
50
51
|
const args = body === undefined
|
|
51
52
|
? ["api", apiPath, "-X", method]
|
|
52
53
|
: ["api", apiPath, "-X", method, "--input", "-"];
|
|
53
|
-
const child = spawn("gh", args, {
|
|
54
|
+
const child = spawn("gh", args, {
|
|
55
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
56
|
+
shell: false,
|
|
57
|
+
});
|
|
54
58
|
let stdout = "";
|
|
55
59
|
let stderr = "";
|
|
56
60
|
child.stdout.on("data", (c) => (stdout += c.toString()));
|
|
@@ -129,7 +133,11 @@ export async function getSelectedActions(r) {
|
|
|
129
133
|
catch (err) {
|
|
130
134
|
if (err instanceof GhSettingsError &&
|
|
131
135
|
(/\b409\b/.test(err.message) || /\(conflict\)/i.test(err.message))) {
|
|
132
|
-
return {
|
|
136
|
+
return {
|
|
137
|
+
github_owned_allowed: false,
|
|
138
|
+
verified_allowed: false,
|
|
139
|
+
patterns_allowed: [],
|
|
140
|
+
};
|
|
133
141
|
}
|
|
134
142
|
throw err;
|
|
135
143
|
}
|
|
@@ -16,7 +16,7 @@ import { confirm } from "@clack/prompts";
|
|
|
16
16
|
import { addDeploymentBranchPolicy, deleteDeploymentBranchPolicy, getActionsPermissions, getAutomatedSecurityFixes, getDeploymentBranchPolicies, getEnvironment, getRepo, getSelectedActions, getVulnerabilityAlertsEnabled, getWorkflowPermissions, patchSecurityAndAnalysis, setActionsPermissions, setAutomatedSecurityFixesEnabled, setDeploymentBranchPolicyMode, setSelectedActions, setVulnerabilityAlertsEnabled, setWorkflowPermissions, } from "./api.js";
|
|
17
17
|
import { buildDesiredSettings } from "./desired.js";
|
|
18
18
|
import { planHasFixableChanges, planSettings, } from "./diff.js";
|
|
19
|
-
import { collectActionUses, computeActionPatterns, unpinnedActionUses } from "./workflows.js";
|
|
19
|
+
import { collectActionUses, computeActionPatterns, unpinnedActionUses, } from "./workflows.js";
|
|
20
20
|
import { findRepoRoot } from "../../repo/root.js";
|
|
21
21
|
import { unwrap } from "../../ui.js";
|
|
22
22
|
const DEFAULT_ORG = "DevDogsUGA";
|
|
@@ -113,16 +113,22 @@ async function confirmApply(yes) {
|
|
|
113
113
|
* `PUT .../actions/permissions`) are written together, once.
|
|
114
114
|
*/
|
|
115
115
|
async function applyPlan(r, desired, live, plan) {
|
|
116
|
-
const drift = new Set(plan.checks
|
|
116
|
+
const drift = new Set(plan.checks
|
|
117
|
+
.filter((c) => c.status === "drift" && c.fixable)
|
|
118
|
+
.map((c) => c.key));
|
|
117
119
|
const secretScanningFields = {};
|
|
118
120
|
if (drift.has("security_and_analysis.secret_scanning")) {
|
|
119
121
|
secretScanningFields.secret_scanning = {
|
|
120
|
-
status: desired.securityAndAnalysis.secretScanning
|
|
122
|
+
status: desired.securityAndAnalysis.secretScanning
|
|
123
|
+
? "enabled"
|
|
124
|
+
: "disabled",
|
|
121
125
|
};
|
|
122
126
|
}
|
|
123
127
|
if (drift.has("security_and_analysis.secret_scanning_push_protection")) {
|
|
124
128
|
secretScanningFields.secret_scanning_push_protection = {
|
|
125
|
-
status: desired.securityAndAnalysis.secretScanningPushProtection
|
|
129
|
+
status: desired.securityAndAnalysis.secretScanningPushProtection
|
|
130
|
+
? "enabled"
|
|
131
|
+
: "disabled",
|
|
126
132
|
};
|
|
127
133
|
}
|
|
128
134
|
if (drift.has("security_and_analysis.secret_scanning_non_provider_patterns")) {
|
|
@@ -134,7 +140,9 @@ async function applyPlan(r, desired, live, plan) {
|
|
|
134
140
|
}
|
|
135
141
|
if (drift.has("security_and_analysis.secret_scanning_validity_checks")) {
|
|
136
142
|
secretScanningFields.secret_scanning_validity_checks = {
|
|
137
|
-
status: desired.securityAndAnalysis.secretScanningValidityChecks
|
|
143
|
+
status: desired.securityAndAnalysis.secretScanningValidityChecks
|
|
144
|
+
? "enabled"
|
|
145
|
+
: "disabled",
|
|
138
146
|
};
|
|
139
147
|
}
|
|
140
148
|
if (Object.keys(secretScanningFields).length > 0) {
|
|
@@ -146,7 +154,8 @@ async function applyPlan(r, desired, live, plan) {
|
|
|
146
154
|
if (drift.has("dependabot_security_updates")) {
|
|
147
155
|
await setAutomatedSecurityFixesEnabled(r, desired.dependabotSecurityUpdates);
|
|
148
156
|
}
|
|
149
|
-
if (drift.has("actions.sha_pinning_required") ||
|
|
157
|
+
if (drift.has("actions.sha_pinning_required") ||
|
|
158
|
+
drift.has("actions.allowed_actions")) {
|
|
150
159
|
await setActionsPermissions(r, {
|
|
151
160
|
enabled: live.actionsPermissions.enabled,
|
|
152
161
|
allowed_actions: drift.has("actions.allowed_actions")
|
|
@@ -28,7 +28,11 @@ export function buildDesiredSettings(actionPatterns) {
|
|
|
28
28
|
},
|
|
29
29
|
environments: [
|
|
30
30
|
{ name: "staging", allowedBranches: ["main"], requireReviewers: false },
|
|
31
|
-
{
|
|
31
|
+
{
|
|
32
|
+
name: "production",
|
|
33
|
+
allowedBranches: ["production"],
|
|
34
|
+
requireReviewers: false,
|
|
35
|
+
},
|
|
32
36
|
{
|
|
33
37
|
name: "production-apply",
|
|
34
38
|
allowedBranches: ["production"],
|
package/dist/gh/settings/diff.js
CHANGED
|
@@ -81,7 +81,8 @@ export function planSettings(desired, live, unpinnedActions) {
|
|
|
81
81
|
: "drift",
|
|
82
82
|
fixable: true,
|
|
83
83
|
});
|
|
84
|
-
const patternsMatch = live.selectedActions.github_owned_allowed ===
|
|
84
|
+
const patternsMatch = live.selectedActions.github_owned_allowed ===
|
|
85
|
+
desired.actions.githubOwnedAllowed &&
|
|
85
86
|
live.selectedActions.verified_allowed === desired.actions.verifiedAllowed &&
|
|
86
87
|
sameSet(live.selectedActions.patterns_allowed, desired.actions.patterns);
|
|
87
88
|
checks.push({
|
|
@@ -118,21 +119,25 @@ export function planSettings(desired, live, unpinnedActions) {
|
|
|
118
119
|
continue;
|
|
119
120
|
}
|
|
120
121
|
const liveBranches = snapshot.branchPolicies.map((p) => p.name);
|
|
121
|
-
const branchesMatch = snapshot.environment.deployment_branch_policy?.custom_branch_policies ===
|
|
122
|
-
|
|
122
|
+
const branchesMatch = snapshot.environment.deployment_branch_policy?.custom_branch_policies ===
|
|
123
|
+
true &&
|
|
124
|
+
snapshot.environment.deployment_branch_policy?.protected_branches ===
|
|
125
|
+
false &&
|
|
123
126
|
sameSet(liveBranches, env.allowedBranches);
|
|
124
127
|
checks.push({
|
|
125
128
|
key: `environments.${env.name}.branch_policy`,
|
|
126
129
|
description: `Environment "${env.name}" deployment branch policy`,
|
|
127
130
|
desired: [...env.allowedBranches].sort().join(", "),
|
|
128
131
|
live: snapshot.environment.deployment_branch_policy
|
|
129
|
-
? [...liveBranches].sort().join(", ") ||
|
|
132
|
+
? [...liveBranches].sort().join(", ") ||
|
|
133
|
+
"(no branch restriction — all branches allowed)"
|
|
130
134
|
: "(no deployment branch policy configured)",
|
|
131
135
|
status: branchesMatch ? "ok" : "drift",
|
|
132
136
|
fixable: true,
|
|
133
137
|
});
|
|
134
138
|
if (env.requireReviewers) {
|
|
135
|
-
const hasReviewers = (snapshot.environment.protection_rules ?? []).some((rule) => rule.type === "required_reviewers" &&
|
|
139
|
+
const hasReviewers = (snapshot.environment.protection_rules ?? []).some((rule) => rule.type === "required_reviewers" &&
|
|
140
|
+
(rule.reviewers?.length ?? 0) > 0);
|
|
136
141
|
checks.push({
|
|
137
142
|
key: `environments.${env.name}.required_reviewers`,
|
|
138
143
|
description: `Environment "${env.name}" required reviewers`,
|
|
@@ -82,7 +82,11 @@ export function parseUsesLines(file, contents) {
|
|
|
82
82
|
}
|
|
83
83
|
out.push({
|
|
84
84
|
raw: value,
|
|
85
|
-
ref: {
|
|
85
|
+
ref: {
|
|
86
|
+
owner: spec.slice(0, slash),
|
|
87
|
+
repo: spec.slice(slash + 1),
|
|
88
|
+
version,
|
|
89
|
+
},
|
|
86
90
|
file,
|
|
87
91
|
line: i + 1,
|
|
88
92
|
});
|
package/dist/images/commands.js
CHANGED
|
@@ -104,7 +104,10 @@ async function loadEvents(patterns, deps) {
|
|
|
104
104
|
throw err;
|
|
105
105
|
return { graphics: [], skipped: errorMessage(err) };
|
|
106
106
|
}
|
|
107
|
-
return {
|
|
107
|
+
return {
|
|
108
|
+
graphics: eventGraphics(await loadOpenGraph(), meetings),
|
|
109
|
+
skipped: null,
|
|
110
|
+
};
|
|
108
111
|
}
|
|
109
112
|
/** Where one selection is written. */
|
|
110
113
|
export function destinationOf({ graphic, format }, out) {
|
|
@@ -183,13 +186,12 @@ async function images(options, deps) {
|
|
|
183
186
|
if (selections.length === 0)
|
|
184
187
|
throw new UsageError("Nothing to render.");
|
|
185
188
|
// ── Where ─────────────────────────────────────────────────────────────────
|
|
189
|
+
// An empty `--out` counts as absent, exactly as the old truthiness test did.
|
|
190
|
+
const explicitOut = options.out === "" ? undefined : options.out;
|
|
186
191
|
const out = options.defaultOut
|
|
187
192
|
? undefined
|
|
188
|
-
:
|
|
189
|
-
?
|
|
190
|
-
: options.noOutput
|
|
191
|
-
? undefined
|
|
192
|
-
: await pickOutput(graphics);
|
|
193
|
+
: (explicitOut ??
|
|
194
|
+
(options.noOutput ? undefined : await pickOutput(graphics)));
|
|
193
195
|
if (skipped)
|
|
194
196
|
log.warn(`event graphics skipped — ${skipped}`);
|
|
195
197
|
if (options.noOutput) {
|
package/dist/images/render.js
CHANGED
|
@@ -37,7 +37,11 @@ async function satoriFonts() {
|
|
|
37
37
|
})));
|
|
38
38
|
}
|
|
39
39
|
export async function render(element, { width, height, scale = 1 }) {
|
|
40
|
-
const svg = await satori(element, {
|
|
40
|
+
const svg = await satori(element, {
|
|
41
|
+
width,
|
|
42
|
+
height,
|
|
43
|
+
fonts: await satoriFonts(),
|
|
44
|
+
});
|
|
41
45
|
const png = new Resvg(svg, {
|
|
42
46
|
fitTo: { mode: "width", value: Math.round(width * scale) },
|
|
43
47
|
// Satori has already turned every glyph into a path, so resvg never needs
|
package/dist/launch-ci.js
CHANGED
|
@@ -54,7 +54,9 @@ export async function launchCi(argv) {
|
|
|
54
54
|
}
|
|
55
55
|
const tier = resolution.tier;
|
|
56
56
|
try {
|
|
57
|
-
const entered = await envSession.enterEnvironment(tier, {
|
|
57
|
+
const entered = await envSession.enterEnvironment(tier, {
|
|
58
|
+
override: false,
|
|
59
|
+
});
|
|
58
60
|
for (const warning of entered.warnings) {
|
|
59
61
|
process.stderr.write(`devtools-ci: ${warning}\n`);
|
|
60
62
|
}
|
package/dist/launch.js
CHANGED
|
@@ -52,7 +52,7 @@ import { select } from "@clack/prompts";
|
|
|
52
52
|
import { findCommand } from "./commands.js";
|
|
53
53
|
import { enterSessionEnvironment, realEnvEntryDeps, setMenuEnvHook, } from "./env-entry.js";
|
|
54
54
|
import { bareGroupStartPath } from "./menu.js";
|
|
55
|
-
import { discoverRepoRoot, findRepoRoot, RepoNotFoundError } from "./repo/root.js";
|
|
55
|
+
import { discoverRepoRoot, findRepoRoot, RepoNotFoundError, } from "./repo/root.js";
|
|
56
56
|
import { loadEnvLoad, loadEnvSession } from "./repo/peers.js";
|
|
57
57
|
import { captureDevtoolsError, initDevtoolsTelemetry } from "./telemetry.js";
|
|
58
58
|
import { ignoreClosedPipes } from "./pipes.js";
|
|
@@ -188,12 +188,12 @@ export async function launch(argv) {
|
|
|
188
188
|
// holds this session's entered values, not the outer shell's, and
|
|
189
189
|
// recomputing from it would misclassify every one of them as a genuine
|
|
190
190
|
// shell export — the exact bug this marker exists to prevent, one layer in.
|
|
191
|
-
|
|
192
|
-
process.env[envLoad.SHELL_KEYS_ENV] = Object.keys(process.env).join(",");
|
|
193
|
-
}
|
|
191
|
+
process.env[envLoad.SHELL_KEYS_ENV] ??= Object.keys(process.env).join(",");
|
|
194
192
|
let tier;
|
|
195
193
|
let devDatabase;
|
|
196
|
-
if (rest[0] === "setup" ||
|
|
194
|
+
if (rest[0] === "setup" ||
|
|
195
|
+
rest[0] === "completions" ||
|
|
196
|
+
isEnvFreeCommand(rest)) {
|
|
197
197
|
// Two commands run BEFORE there is a tier to resolve, and forcing the
|
|
198
198
|
// mandate on them breaks each in its own way:
|
|
199
199
|
//
|
|
@@ -246,7 +246,9 @@ export async function launch(argv) {
|
|
|
246
246
|
const remoteCandidate = couldBeBareDevelopment
|
|
247
247
|
? await envSession.developmentRemoteCandidate(findRepoRoot())
|
|
248
248
|
: undefined;
|
|
249
|
-
const localStackOnline = typeof remoteCandidate === "string"
|
|
249
|
+
const localStackOnline = typeof remoteCandidate === "string"
|
|
250
|
+
? await envLoad.probeLocalStack()
|
|
251
|
+
: undefined;
|
|
250
252
|
const resolution = await envSession.resolveSessionTier({
|
|
251
253
|
explicit,
|
|
252
254
|
deployEnv: process.env.DEPLOY_ENV,
|
package/dist/moderation.js
CHANGED
|
@@ -25,11 +25,13 @@
|
|
|
25
25
|
import { adminClient, signedInClient, } from "./instance.js";
|
|
26
26
|
/** Runs `platform.conformance_check()` as the given moderator client. */
|
|
27
27
|
export async function conformance(client, appSlug) {
|
|
28
|
-
const
|
|
28
|
+
const response = await client.rpc("conformance_check", {
|
|
29
29
|
app_slug: appSlug,
|
|
30
30
|
});
|
|
31
|
-
if (error)
|
|
32
|
-
throw new Error(error.message);
|
|
31
|
+
if (response.error)
|
|
32
|
+
throw new Error(response.error.message);
|
|
33
|
+
// The generated types do not describe this function's return shape.
|
|
34
|
+
const data = response.data;
|
|
33
35
|
return (data ?? []);
|
|
34
36
|
}
|
|
35
37
|
/**
|
package/dist/oauth/db.js
CHANGED
|
@@ -35,7 +35,7 @@ export function detectLocalSupabase(cwd) {
|
|
|
35
35
|
if (first.code === "ENOENT") {
|
|
36
36
|
throw new Error("supabase CLI not found — install it from https://supabase.com/docs/guides/cli");
|
|
37
37
|
}
|
|
38
|
-
const stderr = first.stderr
|
|
38
|
+
const stderr = (first.stderr ?? "").trim();
|
|
39
39
|
throw new Error(stderr || (first instanceof Error ? first.message : String(first)));
|
|
40
40
|
}
|
|
41
41
|
}
|
package/dist/oauth/device.js
CHANGED
|
@@ -50,7 +50,9 @@ export async function requestDeviceCode({ platformUrl, label, callbackUri, fetch
|
|
|
50
50
|
if (response.status === 429) {
|
|
51
51
|
const retryAfterSeconds = retryAfterSecondsFrom(response);
|
|
52
52
|
throw new DeviceError("rate_limited", `${url} is rate-limited` +
|
|
53
|
-
(retryAfterSeconds !== undefined
|
|
53
|
+
(retryAfterSeconds !== undefined
|
|
54
|
+
? ` — retry after ${retryAfterSeconds}s.`
|
|
55
|
+
: "."), retryAfterSeconds);
|
|
54
56
|
}
|
|
55
57
|
let body;
|
|
56
58
|
try {
|
|
@@ -61,8 +63,12 @@ export async function requestDeviceCode({ platformUrl, label, callbackUri, fetch
|
|
|
61
63
|
}
|
|
62
64
|
if (!response.ok) {
|
|
63
65
|
const errBody = (body ?? {});
|
|
64
|
-
const description = typeof errBody.error_description === "string"
|
|
65
|
-
|
|
66
|
+
const description = typeof errBody.error_description === "string"
|
|
67
|
+
? errBody.error_description
|
|
68
|
+
: undefined;
|
|
69
|
+
const code = typeof errBody.error === "string"
|
|
70
|
+
? errBody.error
|
|
71
|
+
: `HTTP ${response.status}`;
|
|
66
72
|
throw new DeviceError("invalid_request", description ? `${code}: ${description}` : code);
|
|
67
73
|
}
|
|
68
74
|
const result = (body ?? {});
|
|
@@ -167,7 +173,9 @@ export async function pollForToken({ platformUrl, deviceCode, intervalSeconds, e
|
|
|
167
173
|
}
|
|
168
174
|
const errBody = (body ?? {});
|
|
169
175
|
const errorCode = typeof errBody.error === "string" ? errBody.error : undefined;
|
|
170
|
-
const description = typeof errBody.error_description === "string"
|
|
176
|
+
const description = typeof errBody.error_description === "string"
|
|
177
|
+
? errBody.error_description
|
|
178
|
+
: undefined;
|
|
171
179
|
switch (errorCode) {
|
|
172
180
|
case "authorization_pending":
|
|
173
181
|
continue;
|
package/dist/oauth/exchange.js
CHANGED
|
@@ -53,7 +53,9 @@ export async function exchangeCode({ platformUrl, code, codeVerifier, }) {
|
|
|
53
53
|
}
|
|
54
54
|
if (response.status === 429) {
|
|
55
55
|
const retryAfterHeader = response.headers.get("retry-after");
|
|
56
|
-
const retryAfterSeconds = retryAfterHeader
|
|
56
|
+
const retryAfterSeconds = retryAfterHeader
|
|
57
|
+
? Number(retryAfterHeader)
|
|
58
|
+
: undefined;
|
|
57
59
|
throw new ExchangeError("rate_limited", `${url} is rate-limited` +
|
|
58
60
|
(Number.isFinite(retryAfterSeconds)
|
|
59
61
|
? ` — retry after ${retryAfterSeconds}s.`
|
|
@@ -69,8 +71,12 @@ export async function exchangeCode({ platformUrl, code, codeVerifier, }) {
|
|
|
69
71
|
if (!response.ok) {
|
|
70
72
|
const errBody = (body ?? {});
|
|
71
73
|
const kind = errBody.error === "invalid_grant" ? "invalid_grant" : "invalid_request";
|
|
72
|
-
const description = typeof errBody.error_description === "string"
|
|
73
|
-
|
|
74
|
+
const description = typeof errBody.error_description === "string"
|
|
75
|
+
? errBody.error_description
|
|
76
|
+
: undefined;
|
|
77
|
+
const code = typeof errBody.error === "string"
|
|
78
|
+
? errBody.error
|
|
79
|
+
: `HTTP ${response.status}`;
|
|
74
80
|
throw new ExchangeError(kind, description ? `${code}: ${description}` : code);
|
|
75
81
|
}
|
|
76
82
|
const result = (body ?? {});
|
package/dist/oauth/loopback.js
CHANGED
|
@@ -66,7 +66,7 @@ export function verifyState(expected, callback) {
|
|
|
66
66
|
return callback;
|
|
67
67
|
}
|
|
68
68
|
function renderCallbackPage(heading, body) {
|
|
69
|
-
return (
|
|
69
|
+
return ('<!doctype html><html><head><meta charset="utf-8"><title>DevDogs devtools</title></head>' +
|
|
70
70
|
`<body style="font-family: system-ui, sans-serif; max-width: 32rem; margin: 4rem auto; text-align: center;">` +
|
|
71
71
|
`<h1>${heading}</h1><p>${body}</p></body></html>`);
|
|
72
72
|
}
|
|
@@ -96,7 +96,7 @@ export function start(opts = {}) {
|
|
|
96
96
|
// moments later — this dummy handler exists purely to be that first
|
|
97
97
|
// listener; it does not consume the rejection for real callers, who
|
|
98
98
|
// still get their own `await`/`.catch` on the same promise.
|
|
99
|
-
result.catch(() =>
|
|
99
|
+
result.catch(() => undefined);
|
|
100
100
|
const server = createServer((req, res) => {
|
|
101
101
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
102
102
|
if (url.pathname !== "/callback") {
|
|
@@ -107,16 +107,22 @@ export function start(opts = {}) {
|
|
|
107
107
|
const state = url.searchParams.get("state") ?? undefined;
|
|
108
108
|
const error = url.searchParams.get("error");
|
|
109
109
|
if (error) {
|
|
110
|
-
res
|
|
110
|
+
res
|
|
111
|
+
.writeHead(200, { "content-type": "text/html; charset=utf-8" })
|
|
112
|
+
.end(DENIED_PAGE);
|
|
111
113
|
settle(() => rejectResult(new CallbackDeniedError(error, state, url.searchParams.get("error_description") ?? undefined)));
|
|
112
114
|
return;
|
|
113
115
|
}
|
|
114
116
|
if (!code || !state) {
|
|
115
|
-
res
|
|
117
|
+
res
|
|
118
|
+
.writeHead(400, { "content-type": "text/html; charset=utf-8" })
|
|
119
|
+
.end(DENIED_PAGE);
|
|
116
120
|
settle(() => rejectResult(new CallbackMalformedError()));
|
|
117
121
|
return;
|
|
118
122
|
}
|
|
119
|
-
res
|
|
123
|
+
res
|
|
124
|
+
.writeHead(200, { "content-type": "text/html; charset=utf-8" })
|
|
125
|
+
.end(SUCCESS_PAGE);
|
|
120
126
|
settle(() => resolveResult({ code, state }));
|
|
121
127
|
});
|
|
122
128
|
const timer = setTimeout(() => {
|
package/dist/oauth/wizard.js
CHANGED
|
@@ -28,7 +28,7 @@ import { openBrowser } from "./browser.js";
|
|
|
28
28
|
import { codeChallengeFor, generateCodeVerifier, generateState, } from "./pkce.js";
|
|
29
29
|
import { CallbackDeniedError, CallbackMalformedError, CallbackTimeoutError, StateMismatchError, start as startLoopback, verifyState, } from "./loopback.js";
|
|
30
30
|
import { buildConnectUrl } from "./connect-url.js";
|
|
31
|
-
import { ExchangeError, exchangeCode } from "./exchange.js";
|
|
31
|
+
import { ExchangeError, exchangeCode, } from "./exchange.js";
|
|
32
32
|
import { DeviceError, PollError, pollForToken, requestDeviceCode, } from "./device.js";
|
|
33
33
|
import { decideTransport } from "./transport.js";
|
|
34
34
|
import { defaultLabel } from "./label.js";
|
|
@@ -70,6 +70,11 @@ function requireUrl(v) {
|
|
|
70
70
|
return "Enter a valid URL";
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
/** An environment variable's value, with an empty one treated as unset. */
|
|
74
|
+
function nonEmptyEnv(key) {
|
|
75
|
+
const value = process.env[key];
|
|
76
|
+
return value === "" ? undefined : value;
|
|
77
|
+
}
|
|
73
78
|
function requireNonEmpty(v) {
|
|
74
79
|
return v?.trim() ? undefined : "Required";
|
|
75
80
|
}
|
|
@@ -101,7 +106,11 @@ async function chooseHostedTargetFromEnvOrPrompt(cwd) {
|
|
|
101
106
|
const fromFiles = readHostedTargetFromEnvFiles(cwd);
|
|
102
107
|
if (fromFiles.apiUrl && fromFiles.serviceRoleKey) {
|
|
103
108
|
log.info("Using the Supabase URL and service-role key from .env.local/.env.");
|
|
104
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
apiUrl: fromFiles.apiUrl,
|
|
111
|
+
serviceRoleKey: fromFiles.serviceRoleKey,
|
|
112
|
+
kind: "hosted",
|
|
113
|
+
};
|
|
105
114
|
}
|
|
106
115
|
const apiUrl = unwrap(await text({ message: "Supabase project URL", validate: requireUrl })).replace(/\/+$/, "");
|
|
107
116
|
const serviceRoleKey = unwrap(await password({
|
|
@@ -343,9 +352,11 @@ export async function connectViaOneClick(target, platformUrl, cwd, transportOver
|
|
|
343
352
|
async function connectByPasting(baseUrlOverride) {
|
|
344
353
|
// Not prompted: contributors always want the canonical API. `--base-url` /
|
|
345
354
|
// `OAUTH_BASE_URL` stay as overrides for testing against another project.
|
|
346
|
-
const baseUrl = (baseUrlOverride ??
|
|
347
|
-
|
|
348
|
-
|
|
355
|
+
const baseUrl = (baseUrlOverride ??
|
|
356
|
+
process.env[ENV_KEYS.baseUrl] ??
|
|
357
|
+
DEFAULT_API_URL).replace(/\/+$/, "");
|
|
358
|
+
let clientId = nonEmptyEnv(ENV_KEYS.clientId);
|
|
359
|
+
let clientSecret = nonEmptyEnv(ENV_KEYS.clientSecret);
|
|
349
360
|
if (clientId && clientSecret) {
|
|
350
361
|
const useSaved = unwrap(await confirm({
|
|
351
362
|
message: `Use saved credentials? (client ID: ${clientId})`,
|
package/dist/persona.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { randomUUID } from "node:crypto";
|
|
30
30
|
import { confirm, log, note, select } from "@clack/prompts";
|
|
31
|
-
import { adminClient, resolveInstance
|
|
31
|
+
import { adminClient, resolveInstance } from "./instance.js";
|
|
32
32
|
import { grantModerator } from "./moderation.js";
|
|
33
33
|
import { explain, unwrap } from "./ui.js";
|
|
34
34
|
export const PERSONA_KINDS = ["member", "moderator"];
|
package/dist/planner/checks.js
CHANGED
|
@@ -37,7 +37,7 @@ export const CHECK_MIGRATIONS = "select count(*)::int as n from supabase_migrati
|
|
|
37
37
|
export async function checkPlanner(db) {
|
|
38
38
|
const lines = [];
|
|
39
39
|
const [identity] = await db.run(CHECK_IDENTITY);
|
|
40
|
-
const who =
|
|
40
|
+
const who = typeof identity?.who === "string" ? identity.who : "";
|
|
41
41
|
if (who !== PLANNER_ROLE) {
|
|
42
42
|
return {
|
|
43
43
|
ok: false,
|
package/dist/planner/commands.js
CHANGED
|
@@ -337,7 +337,7 @@ export async function runPlannerDrop(options = {}) {
|
|
|
337
337
|
return;
|
|
338
338
|
}
|
|
339
339
|
const stored = doc.get("DB_URL");
|
|
340
|
-
if (stored
|
|
340
|
+
if (stored?.includes(PLANNER_ROLE)) {
|
|
341
341
|
doc.set("DB_URL", "");
|
|
342
342
|
await writeFile(path, doc.toString());
|
|
343
343
|
log.info(`Blanked the dead DB_URL in ${fileFor("preflight")}. After the next ` +
|
package/dist/planner/db.js
CHANGED
package/dist/repo/source.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { OpenGraphModule } from "../images/open-graph-types.js";
|
|
|
4
4
|
export declare function loadOpenGraph(): Promise<OpenGraphModule>;
|
|
5
5
|
import type { EmailTemplates } from "../emails/email-types.js";
|
|
6
6
|
export interface EmailModule {
|
|
7
|
-
render<K extends keyof EmailTemplates>(name: K, props: EmailTemplates[K])
|
|
7
|
+
render: <K extends keyof EmailTemplates>(name: K, props: EmailTemplates[K]) => {
|
|
8
8
|
subject: string;
|
|
9
9
|
html: string;
|
|
10
10
|
text: string;
|
package/dist/run/pick.js
CHANGED
|
@@ -417,7 +417,7 @@ async function runDev(plan, filters, rest, tierEnv) {
|
|
|
417
417
|
}));
|
|
418
418
|
// Ctrl-C reaches every child through the terminal's process group; this
|
|
419
419
|
// process waits for them to finish rather than dying first.
|
|
420
|
-
const ignore = () =>
|
|
420
|
+
const ignore = () => undefined;
|
|
421
421
|
process.on("SIGINT", ignore);
|
|
422
422
|
process.on("SIGTERM", ignore);
|
|
423
423
|
const first = await Promise.race(children.map((child) => new Promise((resolve) => {
|
package/dist/setup.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { execFileSync } from "node:child_process";
|
|
18
18
|
import { join } from "node:path";
|
|
19
|
-
import { confirm, log, note, password, select, spinner, text } from "@clack/prompts";
|
|
19
|
+
import { confirm, log, note, password, select, spinner, text, } from "@clack/prompts";
|
|
20
20
|
import { loadRegistry } from "./env/discovery.js";
|
|
21
21
|
import { renderInit, resolveSections } from "./env/example.js";
|
|
22
22
|
import { discoverRepoRoot } from "./repo/root.js";
|
package/dist/telemetry.js
CHANGED
|
@@ -44,7 +44,9 @@ function bakedSentryDsn() {
|
|
|
44
44
|
}
|
|
45
45
|
/** The run-time `DEVTOOLS_SENTRY_DSN` if set, else the baked one. */
|
|
46
46
|
export function resolveDevtoolsDsn(env, baked) {
|
|
47
|
-
|
|
47
|
+
// An empty override (`DEVTOOLS_SENTRY_DSN=`) must fall back to the baked DSN.
|
|
48
|
+
const override = env.DEVTOOLS_SENTRY_DSN ?? "";
|
|
49
|
+
return override || baked;
|
|
48
50
|
}
|
|
49
51
|
let initialized = false;
|
|
50
52
|
/**
|
package/dist/tier.js
CHANGED
|
@@ -50,10 +50,13 @@ export async function resolveTier(given, message, opts = {}) {
|
|
|
50
50
|
// file is missing, which `loadEnvironment` then reports — so it is validated
|
|
51
51
|
// for shape only, not existence, and does NOT fall through to the picker.
|
|
52
52
|
const entered = opts.deployEnv ?? process.env.DEPLOY_ENV;
|
|
53
|
-
if (entered !== undefined &&
|
|
53
|
+
if (entered !== undefined &&
|
|
54
|
+
entered !== "" &&
|
|
55
|
+
env.isDeployEnvironment(entered)) {
|
|
54
56
|
return entered;
|
|
55
57
|
}
|
|
56
|
-
const available = opts.available ??
|
|
58
|
+
const available = opts.available ??
|
|
59
|
+
(await (await loadEnvSession()).availableTiers(findRepoRoot()));
|
|
57
60
|
// Zero or one tier file present: there is nothing to choose between, so
|
|
58
61
|
// asking would be a question with one possible answer. Fall through to
|
|
59
62
|
// whichever tier is present, or development when nothing is (a machine
|
package/dist/version.js
CHANGED
|
@@ -6,9 +6,7 @@ let cachedOwnDir;
|
|
|
6
6
|
* i.e. the devtools package's own root (`dist/version.js` → up one).
|
|
7
7
|
* Exported for tests; memoized since it never changes within a process. */
|
|
8
8
|
export function ownPackageDir() {
|
|
9
|
-
|
|
10
|
-
cachedOwnDir = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
-
}
|
|
9
|
+
cachedOwnDir ??= join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
10
|
return cachedOwnDir;
|
|
13
11
|
}
|
|
14
12
|
/** Reads this build's own `package.json` version — used by `telemetry.ts`
|
package/dist/workers.js
CHANGED
|
@@ -21,9 +21,7 @@ import { findRepoRoot } from "./repo/root.js";
|
|
|
21
21
|
let cachedWorkerPaths;
|
|
22
22
|
/** Workspace-relative paths, exactly as listed in root `workers.json`. */
|
|
23
23
|
export function workerPaths() {
|
|
24
|
-
|
|
25
|
-
cachedWorkerPaths = JSON.parse(readFileSync(join(findRepoRoot(), "workers.json"), "utf8"));
|
|
26
|
-
}
|
|
24
|
+
cachedWorkerPaths ??= JSON.parse(readFileSync(join(findRepoRoot(), "workers.json"), "utf8"));
|
|
27
25
|
return cachedWorkerPaths;
|
|
28
26
|
}
|
|
29
27
|
/** Basenames, e.g. `"schedule-builder"` — the `pnpm --filter` / CLI slug. */
|
|
@@ -7,7 +7,7 @@ import { basename, join } from "node:path";
|
|
|
7
7
|
import { setTimeout as delay } from "node:timers/promises";
|
|
8
8
|
import { confirm, select, text } from "@clack/prompts";
|
|
9
9
|
import { loadEnvLoad } from "../repo/peers.js";
|
|
10
|
-
import { createTemporaryWranglerEnv,
|
|
10
|
+
import { createTemporaryWranglerEnv, scopedProcessEnv, } from "../cf/local-env.js";
|
|
11
11
|
import { buildWorkspaceDeps, needsFrameworkBuild } from "../cf/build.js";
|
|
12
12
|
import { runWithStderr } from "../db/run.js";
|
|
13
13
|
import { findRepoRoot } from "../repo/root.js";
|
|
@@ -614,7 +614,8 @@ export async function runWorkflowsRun(argv) {
|
|
|
614
614
|
let triggerEnv;
|
|
615
615
|
if (tier !== "development") {
|
|
616
616
|
try {
|
|
617
|
-
triggerEnv = (await envLoad.loadEnvironment(tier, { override: true }))
|
|
617
|
+
triggerEnv = (await envLoad.loadEnvironment(tier, { override: true }))
|
|
618
|
+
.env;
|
|
618
619
|
}
|
|
619
620
|
catch (err) {
|
|
620
621
|
if (err instanceof envLoad.MissingEnvFileError) {
|
package/env.ts
CHANGED
|
@@ -78,11 +78,11 @@ declare({
|
|
|
78
78
|
.optional(),
|
|
79
79
|
{
|
|
80
80
|
doc:
|
|
81
|
-
"The Cloudflare account the Workers live in.
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
81
|
+
"The Cloudflare account the Workers live in. Wrangler honors this " +
|
|
82
|
+
"public identifier in the environment; each app's wrangler config " +
|
|
83
|
+
"pins the same value so scoped deploy tokens never need account " +
|
|
84
|
+
"discovery. Identifies, does not authorize — every capability is " +
|
|
85
|
+
"the token's.",
|
|
86
86
|
scope: "default",
|
|
87
87
|
secrecy: "public",
|
|
88
88
|
example: "61d185ff419ef7bd5bd4b3d314081a49",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devdogsuga/devtools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Contributor CLI for a DevDogsUGA checkout: database, env, images, emails, cron, deploy. Ships built JS; run via `pnpm dlx @devdogsuga/devtools` (or the `devtools`/`devtools-ci` bins once installed) from inside a DevDogsUGA clone.",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"tsx": "^4.23.12",
|
|
39
39
|
"typescript": "^6.0.3",
|
|
40
40
|
"zod": "4.4.3",
|
|
41
|
-
"@devdogsuga/telemetry": "0.1.
|
|
41
|
+
"@devdogsuga/telemetry": "0.1.2"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@devdogsuga/brand": "*",
|
|
@@ -64,13 +64,14 @@
|
|
|
64
64
|
"@types/node": "^26.2.0",
|
|
65
65
|
"@types/react": "^19.2.18",
|
|
66
66
|
"@vitest/coverage-v8": "^4.1.11",
|
|
67
|
+
"eslint": "^9.39.5",
|
|
67
68
|
"react": "^19.2.8",
|
|
68
69
|
"vitest": "^4.1.11",
|
|
69
|
-
"@devdogsuga/brand": "0.1.
|
|
70
|
-
"@devdogsuga/config": "0.1.
|
|
71
|
-
"@devdogsuga/
|
|
70
|
+
"@devdogsuga/brand": "0.1.3",
|
|
71
|
+
"@devdogsuga/config": "0.1.2",
|
|
72
|
+
"@devdogsuga/events": "0.1.3",
|
|
72
73
|
"@devdogsuga/env": "0.1.2",
|
|
73
|
-
"@devdogsuga/
|
|
74
|
+
"@devdogsuga/db": "0.1.2"
|
|
74
75
|
},
|
|
75
76
|
"scripts": {
|
|
76
77
|
"build": "tsc && node scripts/write-build-info.mjs",
|
|
@@ -80,6 +81,8 @@
|
|
|
80
81
|
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
|
|
81
82
|
"test": "vitest run",
|
|
82
83
|
"test:watch": "vitest",
|
|
83
|
-
"test:contract": "vitest run --config vitest.contract.config.ts"
|
|
84
|
+
"test:contract": "vitest run --config vitest.contract.config.ts",
|
|
85
|
+
"lint": "eslint .",
|
|
86
|
+
"lint:fix": "eslint . --fix"
|
|
84
87
|
}
|
|
85
88
|
}
|