gencow 0.1.266 → 0.1.268

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.
@@ -150,7 +150,7 @@ const SAFE_LOCAL_ERROR_CODES = new Map([
150
150
  action: (caught) => {
151
151
  const dir = safeDisplayDir(caught);
152
152
  const target = dir ?? "<build-output-dir>";
153
- return `Retryable: yes, after the build writes an index.html with real content into ${target} — run \`ls -l ${target}/index.html\` to confirm its size is above 0 bytes and that it holds more than whitespace or HTML comments, then rerun \`gencow static ${target}\`.`;
153
+ return `Retryable: yes, after the build writes an index.html with real content into ${target} — run \`ls -l ${target}/index.html\` to confirm its size is above 0 bytes and that it holds more than whitespace or HTML comments, then rerun \`gencow static ${target}${safeRetryTargetFlags(caught)}\`.`;
154
154
  },
155
155
  },
156
156
  ],
@@ -162,7 +162,7 @@ const SAFE_LOCAL_ERROR_CODES = new Map([
162
162
  action: (caught) => {
163
163
  const dir = safeDisplayDir(caught);
164
164
  const target = dir ?? "<build-output-dir>";
165
- return `Retryable: yes, after the build writes index.html into ${target} — run \`ls ${target}/index.html\` to confirm, then rerun \`gencow static ${target}\`.`;
165
+ return `Retryable: yes, after the build writes index.html into ${target} — run \`ls ${target}/index.html\` to confirm, then rerun \`gencow static ${target}${safeRetryTargetFlags(caught)}\`.`;
166
166
  },
167
167
  },
168
168
  ],
@@ -172,6 +172,11 @@ function safeDisplayDir(caught) {
172
172
  const { displayDir } = caught;
173
173
  return typeof displayDir === "string" && SAFE_DISPLAY_DIR.test(displayDir) ? displayDir : null;
174
174
  }
175
+
176
+ // Only the production selector is carried into retry guidance; any other value is never printed.
177
+ function safeRetryTargetFlags(caught) {
178
+ return caught.targetFlags === "--prod" ? " --prod" : "";
179
+ }
175
180
  const SAFE_SOURCE_LOCATION_PATH = /^(?![/\\])(?!.*(?:^|\/)\.\.(?:\/|$))[^\u0000-\u001f\u007f]{1,240}$/u;
176
181
 
177
182
  const SAFE_IMPORT_NAME = /^[A-Za-z_$][\w$]{0,127}$/u;
@@ -7,6 +7,7 @@ import { fetchAppDiagnosticLogsViaRest, normalizeDiagnosticLogLines } from "./ap
7
7
  import { createCloudAppIfNeeded as createCloudAppIfNeededImpl } from "./deploy-app-creation.mjs";
8
8
  import { rejectExplicitAppNotFound } from "./deploy-app-target.mjs";
9
9
  import { loadProjectConfigForDeploy } from "./deploy-project-config-load.mjs";
10
+ import { loadPackageJson } from "./deploy-project-package-json.mjs";
10
11
  import { resolveConfiguredAuditorImplementation } from "./auditor-mode.mjs";
11
12
  import {
12
13
  loadDeployMigrationGenerationContext,
@@ -398,12 +399,13 @@ export function createDeployPackageRuntime({
398
399
  warnRuntimePathsOutsideBackendRoot({ entryPoint, projectRoot: cwd, backendRoot, warnImpl });
399
400
  let dependencyAuditManifest = null;
400
401
  let dependencyVersionMessage = "";
401
- let projectPkg;
402
+ const packageJson = loadPackageJson({ cwd, readFileSyncImpl, resolvePathImpl, errorImpl, exitImpl });
403
+ if (!packageJson) return null;
404
+ const projectPkg = packageJson.pkg;
402
405
  try {
403
406
  const { auditDependencyVersions, formatDependencyVersionAuditError } = await (
404
407
  loadDeployAuditorImpl ?? loadDeployAuditorDefault
405
408
  )();
406
- projectPkg = JSON.parse(readFileSyncImpl(resolvePathImpl(cwd, "package.json"), "utf8"));
407
409
  const auditOptions = { cwd, existsSyncImpl, readFileSyncImpl };
408
410
  const dependencyVersionResult = auditDependencyVersions(projectPkg, auditOptions);
409
411
  dependencyVersionMessage = formatDependencyVersionAuditError(dependencyVersionResult);
@@ -447,9 +449,6 @@ export function createDeployPackageRuntime({
447
449
  }
448
450
  }
449
451
 
450
- // Keep the manifest byte-compatible with the canonical pnpm lock importer.
451
- const stagedPackageJson = readFileSyncImpl(resolvePathImpl(cwd, "package.json"), "utf8");
452
-
453
452
  let bundleBackendRoot;
454
453
  let bundleFileRows = [];
455
454
  try {
@@ -472,7 +471,7 @@ export function createDeployPackageRuntime({
472
471
  readFileSyncImpl,
473
472
  resolvePathImpl,
474
473
  rmSyncImpl,
475
- stagedPackageJson,
474
+ stagedPackageJson: packageJson.text,
476
475
  tarCreateImpl,
477
476
  tmpBundle,
478
477
  writeCronManifestForProjectImpl,
@@ -0,0 +1,79 @@
1
+ // Reads the project package.json for deploy packaging. A missing, unreadable or non-JSON file is reported
2
+ // as a typed, secret-free diagnosis (no raw fs/parser text, no absolute workspace path) and returns null.
3
+
4
+ export const DEPLOY_PACKAGE_JSON_CODES = Object.freeze({
5
+ MISSING: "DEPLOY_PACKAGE_JSON_MISSING",
6
+ UNREADABLE: "DEPLOY_PACKAGE_JSON_UNREADABLE",
7
+ INVALID: "DEPLOY_PACKAGE_JSON_INVALID",
8
+ });
9
+
10
+ const NEXT_ACTION_FOR_MISSING =
11
+ "Next action: for a static-only site, run `gencow static --prod <build-output-dir>` (for example `gencow static --prod dist/`); for a backend app, run `npx gencow@latest init` to create a project with a package.json, then rerun deploy.";
12
+
13
+ const DIAGNOSES = Object.freeze({
14
+ [DEPLOY_PACKAGE_JSON_CODES.MISSING]: {
15
+ message: "DEPLOY BLOCKED — package.json was not found in the project root.",
16
+ retryable:
17
+ "Retryable: yes, after package.json exists in the project root — run `ls package.json` to confirm, then rerun the same deploy command.",
18
+ nextAction: NEXT_ACTION_FOR_MISSING,
19
+ },
20
+ [DEPLOY_PACKAGE_JSON_CODES.UNREADABLE]: {
21
+ message: "DEPLOY BLOCKED — package.json exists but could not be read.",
22
+ retryable:
23
+ "Retryable: yes, after package.json is readable by the current user — check its file permissions, then rerun the same deploy command.",
24
+ nextAction:
25
+ "Next action: make package.json a readable regular file in the project root, then rerun deploy.",
26
+ },
27
+ [DEPLOY_PACKAGE_JSON_CODES.INVALID]: {
28
+ message: "DEPLOY BLOCKED — package.json is not a valid JSON object.",
29
+ retryable:
30
+ "Retryable: yes, after package.json is fixed — run `node -p \"require('./package.json').name\"` to see the syntax error, correct the file, then rerun the same deploy command.",
31
+ nextAction:
32
+ "Next action: correct the JSON syntax of package.json in the project root, then rerun deploy.",
33
+ },
34
+ });
35
+
36
+ export function formatDeployPackageJsonDiagnostic(code) {
37
+ const diagnosis = DIAGNOSES[code];
38
+ return [
39
+ diagnosis.message,
40
+ `Code: ${code}`,
41
+ "Stage: packaging",
42
+ "Database mutation: NONE",
43
+ "Incumbent serving: PRESERVED",
44
+ diagnosis.retryable,
45
+ diagnosis.nextAction,
46
+ ].join("\n");
47
+ }
48
+
49
+ function isPlainObject(value) {
50
+ return value !== null && typeof value === "object" && !Array.isArray(value);
51
+ }
52
+
53
+ // Returns { pkg, text } (text is the exact file content, so the staged manifest stays byte-identical to what
54
+ // was validated), or null after printing the typed diagnosis and exiting with code 1.
55
+ export function loadPackageJson({ cwd, readFileSyncImpl, resolvePathImpl, errorImpl, exitImpl }) {
56
+ let text;
57
+ let code = null;
58
+ try {
59
+ text = readFileSyncImpl(resolvePathImpl(cwd, "package.json"), "utf8");
60
+ } catch (caught) {
61
+ code =
62
+ caught?.code === "ENOENT" ? DEPLOY_PACKAGE_JSON_CODES.MISSING : DEPLOY_PACKAGE_JSON_CODES.UNREADABLE;
63
+ }
64
+ let pkg;
65
+ if (code === null) {
66
+ try {
67
+ pkg = JSON.parse(text);
68
+ } catch {
69
+ pkg = undefined;
70
+ }
71
+ if (!isPlainObject(pkg)) code = DEPLOY_PACKAGE_JSON_CODES.INVALID;
72
+ }
73
+ if (code !== null) {
74
+ errorImpl(formatDeployPackageJsonDiagnostic(code));
75
+ exitImpl(1);
76
+ return null;
77
+ }
78
+ return { pkg, text };
79
+ }
@@ -402,6 +402,8 @@ export function createDeployCommand({
402
402
  try {
403
403
  staticArtifactPreflight = preflightStaticDeployArtifactImpl({
404
404
  staticDirArg: resolvedStaticDirArg,
405
+ // Explicit --prod only; an --app that implies prod is resolved after this point.
406
+ targetFlags: parsed.envTarget === "prod" ? "--prod" : null,
405
407
  // Display text is what the user typed or the client config says, never the resolved path.
406
408
  staticDirDisplay: parsed.staticDirArg ?? selectedClient?.static?.dir ?? null,
407
409
  staticDirSource: parsed.staticDirArg ? "argument" : "config",
@@ -319,6 +319,7 @@ export async function runStaticDeployFlow({
319
319
  try {
320
320
  artifactPreflight = preflightStaticDeployArtifactImpl({
321
321
  staticDirArg,
322
+ targetFlags: envTarget === "prod" ? "--prod" : null,
322
323
  cwd: frontendCwd,
323
324
  existsSyncImpl,
324
325
  resolvePathImpl,
@@ -74,6 +74,7 @@ export function buildStaticArtifactManifest({
74
74
  rootDir,
75
75
  selection,
76
76
  displayDir = null,
77
+ targetFlags = null,
77
78
  lstatSyncImpl = lstatSync,
78
79
  readdirSyncImpl = readdirSync,
79
80
  readFileSyncImpl = readFileSync,
@@ -99,19 +100,22 @@ export function buildStaticArtifactManifest({
99
100
  : "Selected directory";
100
101
  // displayDir is the project-relative directory the user passed or the CLI detected. It is
101
102
  // attached as a field (not interpolated) so the public renderer decides what is safe to show.
103
+ // targetFlags carries only the production selector the user passed, so the retry guidance can keep
104
+ // it. Any other value (for example an app ID) is dropped here and never reaches the message.
105
+ const retryTarget = targetFlags === "--prod" ? { targetFlags } : {};
102
106
  if (entrypoint === "empty") {
103
107
  throw Object.assign(
104
108
  new Error(
105
109
  `${source} has an empty or blank index.html at its root (only whitespace or HTML comments). Refusing to replace the current frontend.`,
106
110
  ),
107
- { code: "STATIC_ARTIFACT_INDEX_EMPTY", displayDir },
111
+ { code: "STATIC_ARTIFACT_INDEX_EMPTY", displayDir, ...retryTarget },
108
112
  );
109
113
  }
110
114
  throw Object.assign(
111
115
  new Error(
112
116
  `${source} does not contain index.html at its root. Refusing to replace the current frontend.`,
113
117
  ),
114
- { code: "STATIC_ARTIFACT_INDEX_MISSING", displayDir },
118
+ { code: "STATIC_ARTIFACT_INDEX_MISSING", displayDir, ...retryTarget },
115
119
  );
116
120
  }
117
121
  return {
@@ -263,12 +263,24 @@ export function createStaticCommand(options = {}) {
263
263
 
264
264
  // The display value is what the user typed or the client config says, never the resolved absolute path.
265
265
  const staticDirDisplay = parsed.staticDir || selectedClient?.static?.dir || null;
266
+ // The production selector the user asked for (--prod, or --app naming the prod app) is passed to the
267
+ // artifact error so its Retry line keeps it. The app ID itself is never passed.
268
+ const requestedTargetFlags =
269
+ resolveImplicitProdEnvironment({
270
+ explicitApp: Boolean(parsed.appId),
271
+ envTarget: parsed.isProd ? "prod" : "dev",
272
+ appId: parsed.appId,
273
+ prodApp: prodAppId,
274
+ }) === "prod"
275
+ ? "--prod"
276
+ : null;
266
277
  let artifactPreflight;
267
278
  try {
268
279
  artifactPreflight = preflightStaticDeployArtifactImpl({
269
280
  staticDirArg: configuredStaticDir,
270
281
  staticDirDisplay,
271
282
  staticDirSource: parsed.staticDir ? "argument" : "config",
283
+ targetFlags: requestedTargetFlags,
272
284
  cwd: selectedClient ? frontendRoot : cwdImpl(),
273
285
  });
274
286
  } catch (manifestError) {
@@ -52,6 +52,7 @@ export function preflightStaticDeployArtifact({
52
52
  staticDirArg,
53
53
  staticDirDisplay = null,
54
54
  staticDirSource = "argument",
55
+ targetFlags = null,
55
56
  cwd = process.cwd(),
56
57
  autoDetectDirs,
57
58
  existsSyncImpl = existsSync,
@@ -78,6 +79,7 @@ export function preflightStaticDeployArtifact({
78
79
  rootDir: resolvePathImpl(cwd, targetDir),
79
80
  selection: staticDirArg ? "explicit" : `auto:${targetDir}`,
80
81
  displayDir: targetDir,
82
+ targetFlags,
81
83
  lstatSyncImpl,
82
84
  readdirSyncImpl,
83
85
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencow",
3
- "version": "0.1.266",
3
+ "version": "0.1.268",
4
4
  "description": "Gencow — AI Backend Engine",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,8 +42,8 @@
42
42
  "postgres": "^3.4.9",
43
43
  "@gencow/client": "0.2.8",
44
44
  "@gencow/react": "0.2.8",
45
- "@gencow/server": "0.1.3",
46
45
  "@gencow/core": "0.1.47",
46
+ "@gencow/server": "0.1.3",
47
47
  "@gencow/shared": "0.0.0"
48
48
  },
49
49
  "scripts": {