nerdsystem 0.0.28 → 0.0.29

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.
@@ -18,10 +18,22 @@ export interface BuildOutput {
18
18
  */
19
19
  export declare function dropLocalSecretCopies(build: BuildOutput): number;
20
20
  /**
21
- * The build's own output lists that copy ("Using secrets defined in .dev.vars", then the file among what
22
- * it wrote), which reads as if local secrets were about to be uploaded. Said once, right after the build.
21
+ * The build's own output names that copy twice ("Using secrets defined in .dev.vars", then the file among
22
+ * what it wrote), which reads as if local secrets were about to be uploaded. This sentence is printed in
23
+ * place of those lines, so the explanation is where the alarm would have been. True before and after the copy is deleted.
23
24
  */
24
- export declare const SECRET_COPY_NOTE = "The build tool listed a copy of your local secrets file (.dev.vars) in its output folder; it makes one for local previews. nerd has deleted that copy. It is never uploaded: the live app gets its secrets from `nerd secret set`.";
25
+ export declare const SECRET_COPY_NOTE = "The build tool makes a copy of your local secrets file (.dev.vars) in its output folder, for local previews. nerd deletes that copy when the build ends and never uploads it: the live app gets its secrets from `nerd secret set`.";
26
+ /**
27
+ * Every check of a deploy as one list, so its closing count is the one `nerd check` gives for the same
28
+ * code: the scan of the built output is in it, and a check that ran on the code and again on the built
29
+ * output (the policy's size limits) is one check, with the later answer.
30
+ */
31
+ export declare function wholeRun(source: GateResult[], built: GateResult[]): GateResult[];
32
+ /** For the build's output: the lines that name the local secrets file become the one sentence above, said once. */
33
+ export declare function secretCopyLines(): {
34
+ line: (text: string) => string | null;
35
+ said: () => boolean;
36
+ };
25
37
  export declare function readBuildOutput(root: string): BuildOutput;
26
38
  export declare function applyRemoteMigrations(root: string, api: ApiClient): Promise<{
27
39
  applied: string[];
@@ -43,10 +43,34 @@ export function dropLocalSecretCopies(build) {
43
43
  return dropped;
44
44
  }
45
45
  /**
46
- * The build's own output lists that copy ("Using secrets defined in .dev.vars", then the file among what
47
- * it wrote), which reads as if local secrets were about to be uploaded. Said once, right after the build.
46
+ * The build's own output names that copy twice ("Using secrets defined in .dev.vars", then the file among
47
+ * what it wrote), which reads as if local secrets were about to be uploaded. This sentence is printed in
48
+ * place of those lines, so the explanation is where the alarm would have been. True before and after the copy is deleted.
48
49
  */
49
- export const SECRET_COPY_NOTE = "The build tool listed a copy of your local secrets file (.dev.vars) in its output folder; it makes one for local previews. nerd has deleted that copy. It is never uploaded: the live app gets its secrets from `nerd secret set`.";
50
+ export const SECRET_COPY_NOTE = "The build tool makes a copy of your local secrets file (.dev.vars) in its output folder, for local previews. nerd deletes that copy when the build ends and never uploads it: the live app gets its secrets from `nerd secret set`.";
51
+ /**
52
+ * Every check of a deploy as one list, so its closing count is the one `nerd check` gives for the same
53
+ * code: the scan of the built output is in it, and a check that ran on the code and again on the built
54
+ * output (the policy's size limits) is one check, with the later answer.
55
+ */
56
+ export function wholeRun(source, built) {
57
+ return [...source.filter((r) => !built.some((b) => b.name === r.name)), ...built];
58
+ }
59
+ /** For the build's output: the lines that name the local secrets file become the one sentence above, said once. */
60
+ export function secretCopyLines() {
61
+ let said = false;
62
+ return {
63
+ line: (text) => {
64
+ if (!/\.dev\.vars\b/.test(text))
65
+ return text;
66
+ if (said)
67
+ return null;
68
+ said = true;
69
+ return SECRET_COPY_NOTE;
70
+ },
71
+ said: () => said,
72
+ };
73
+ }
50
74
  export function readBuildOutput(root) {
51
75
  const dist = path.join(root, "dist");
52
76
  if (!existsSync(dist))
@@ -241,18 +265,20 @@ export async function deploy(opts) {
241
265
  const gates = await runGates(SOURCE_GATES, { root, knownSecrets, approvedSuppressions: await approvedSuppressions(root), log: (l) => log.info(l) }, { stopOnFail: true });
242
266
  if (!gates.ok)
243
267
  throw new CliError(`Blocked by: ${gates.failed.map((g) => g.title).join(", ")}. Fix the findings above, then deploy again.`, 2);
244
- log.ok(gateSummary(gates.results));
268
+ const secretCopy = secretCopyLines();
245
269
  if (!opts["skip-build"]) {
246
270
  log.step("Building with vite");
247
271
  ensureDeps(root);
248
- await runOrThrow("npx", ["vite", "build"], { cwd: root, stdoutToStderr: true });
272
+ await runOrThrow("npx", ["vite", "build"], { cwd: root, stdoutToStderr: true, stdoutLine: secretCopy.line });
249
273
  }
250
274
  const build = readBuildOutput(root);
251
- if (dropLocalSecretCopies(build))
275
+ // Said here only when the build's output did not carry it (a build made earlier, or a tool that words it differently).
276
+ if (dropLocalSecretCopies(build) && !secretCopy.said())
252
277
  log.info(SECRET_COPY_NOTE);
253
278
  const built = await runGates(BUILD_GATES, { root, build, knownSecrets, log: (l) => log.info(l) }, { stopOnFail: true });
254
279
  if (!built.ok)
255
280
  throw new CliError(`Blocked by: ${built.failed.map((g) => g.title).join(", ")}.`, 2);
281
+ log.ok(gateSummary(wholeRun(gates.results, built.results)));
256
282
  const r = await shipBuild({ root, nerd, api, build, results: [...gates.results, ...built.results], opts });
257
283
  // The app is live at this point. The platform says what the review on file holds, and now and then reads the code itself.
258
284
  const reviewPage = `${dashboardUrl(api.apiUrl)}/app/${nerd.org}/projects/${nerd.project}/review`;
@@ -192,4 +192,4 @@ export async function init(dir, args) {
192
192
  log.ok(`Ready. Next:\n cd ${dir}\n pnpm install\n nerd dev\n\nThen open the folder in your coding agent and ask it to build the first feature; the nerdsystem skill walks it through.\nDeploys go to ${project.url}`);
193
193
  }
194
194
  // Kept in sync with packages/sdk/package.json by the release script.
195
- const SDK_VERSION = "0.0.28";
195
+ const SDK_VERSION = "0.0.29";
package/dist/util.d.ts CHANGED
@@ -22,6 +22,11 @@ export interface RunOptions {
22
22
  env?: NodeJS.ProcessEnv;
23
23
  /** Send the child's stdout to our stderr, so stdout stays reserved for `--json` output. */
24
24
  stdoutToStderr?: boolean;
25
+ /**
26
+ * With `stdoutToStderr`: each line the child prints is passed through this first. Return the line to
27
+ * print it, another line to print that instead, or null to print nothing.
28
+ */
29
+ stdoutLine?: (line: string) => string | null;
25
30
  }
26
31
  /**
27
32
  * How a command is started. On Windows the tools an app uses (npx, pnpm) are `.cmd` files, which only
package/dist/util.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
3
  import path from "node:path";
4
+ import { createInterface } from "node:readline";
4
5
  export class CliError extends Error {
5
6
  exitCode;
6
7
  /** The API's error code (e.g. "not_found") when the error came from a response; callers branch on this, never on the message. */
@@ -40,9 +41,19 @@ export function run(cmd, args, opts = {}) {
40
41
  return new Promise((resolve, reject) => {
41
42
  const how = shellCommand(cmd, args);
42
43
  const child = spawn(how.file, how.args, { cwd: opts.cwd, env: { ...process.env, ...opts.env }, stdio: ["inherit", opts.stdoutToStderr ? "pipe" : "inherit", "inherit"], shell: how.shell });
43
- child.stdout?.pipe(process.stderr);
44
+ const perLine = child.stdout && opts.stdoutLine ? opts.stdoutLine : null;
45
+ if (perLine) {
46
+ createInterface({ input: child.stdout }).on("line", (line) => {
47
+ const out = perLine(line);
48
+ if (out !== null)
49
+ process.stderr.write(`${out}\n`);
50
+ });
51
+ }
52
+ else
53
+ child.stdout?.pipe(process.stderr);
44
54
  child.on("error", reject);
45
- child.on("exit", (code) => resolve(code ?? 1));
55
+ // "close" comes after the child's output has ended, so every line has been through `stdoutLine` by then.
56
+ child.on(perLine ? "close" : "exit", (code) => resolve(code ?? 1));
46
57
  });
47
58
  }
48
59
  export async function runOrThrow(cmd, args, opts = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nerdsystem",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "nerd: start, check, and deploy a web app on nerdsystem from the terminal or a coding agent.",
5
5
  "type": "module",
6
6
  "bin": {