Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/scaffold-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@ jobs:
fail-fast: false
matrix:
language: [typescript, python, go]
name: scaffold-and-compile (${{ matrix.language }})
# Empty default so the include below adds a 4th leg instead of
# attaching the fixture to the existing typescript leg.
fixture: [""]
include:
# Multi-FK prod schema that once failed the in-browser build.
- language: typescript
fixture: test/fixtures/inventory-multi-fk.apsorc
name: scaffold-and-compile (${{ matrix.language }}${{ matrix.fixture && ' + fixture' || '' }})
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -50,4 +57,6 @@ jobs:
python-version: "3.12"

- name: Scaffold and compile
env:
APSORC_FIXTURE: ${{ matrix.fixture }}
run: bash scripts/scaffold-smoke.sh "${{ matrix.language }}"
13 changes: 12 additions & 1 deletion scripts/scaffold-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#
# Exit non-zero on the first failure so CI surfaces a broken scaffold.
# Run locally: bash scripts/scaffold-smoke.sh [typescript|python|go]
# Optional: APSORC_FIXTURE=test/fixtures/<file>.apsorc to generate from a fixture.
set -euo pipefail

LANGUAGE="${1:-typescript}"
Expand All @@ -31,7 +32,17 @@ cd "$WORKDIR"
"$CLI_ROOT/bin/run" init --name smoke-app --language "$LANGUAGE" --skip-platform
cd smoke-app

if [ -f .apsorc ]; then
if [ -n "${APSORC_FIXTURE:-}" ]; then
# A real-world schema (e.g. test/fixtures/inventory-multi-fk.apsorc: 9
# entities, 13 relationships, several FKs to the same entity) instead of
# the one-entity seed below.
echo "==> Using fixture schema $APSORC_FIXTURE"
cp "$CLI_ROOT/$APSORC_FIXTURE" .apsorc
"$CLI_ROOT/bin/run" generate --language "$LANGUAGE" || {
echo "FAIL: apso generate errored on $APSORC_FIXTURE"
exit 1
}
elif [ -f .apsorc ]; then
# Templates ship with "entities": [] and every generator rejects an empty
# entity list, so seed one small entity. This also turns the run into a real
# codegen check: the generated files must compile below.
Expand Down
13 changes: 13 additions & 0 deletions src/commands/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { isInteractive, missingFlag } from "../lib/utils/interactive";
import { performance } from "perf_hooks";
import { createFile } from "../lib/utils/file-system";
import { installCoAuthorHook } from "../lib/utils/git-hooks";
import { ensureCrudDeps } from "../lib/utils/crud-deps";

export default class Generate extends BaseCommand {
static description = "Generate code from .apsorc schema";
Expand Down Expand Up @@ -312,6 +313,18 @@ export default class Generate extends BaseCommand {
console.log("[apso] Skipping formatting (no files were generated)");
}

// Generated TS code imports @apso/crud*. Projects from the v1 template
// (@nestjsx/crud) don't declare those, and every entity then fails to
// compile with TS2307. Declare them so the next install picks them up.
if (language === "typescript" && generatedFileCount > 0) {
const added = ensureCrudDeps(process.cwd());
if (added.length > 0) {
console.log(
`[apso] Added ${added.join(", ")} to package.json (the generated code imports them). Run npm install before building.`
);
}
}

const totalBuildTime = performance.now() - totalBuildStart;
console.log(
`[apso] Finished building all entities in ${totalBuildTime.toFixed(2)} ms`
Expand Down
44 changes: 44 additions & 0 deletions src/lib/utils/crud-deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as fs from "fs";
import * as path from "path";

/**
* Runtime packages the generated TypeScript code imports (entities, services,
* controllers, guards). Versions match the pinned service-template-ts tag
* (see TEMPLATE_REFS in ./template.ts); bump both together.
*/
export const APSO_CRUD_DEPS: Record<string, string> = {
"@apso/crud": "^1.0.1",
"@apso/crud-core": "^1.0.1",
"@apso/crud-request": "^1.0.1",
"@apso/crud-typeorm": "^1.0.1",
};

/**
* Add any missing @apso/crud runtime packages to the project's package.json.
*
* Projects created from the v1 template (@nestjsx/crud) don't declare them, so
* freshly generated code fails `nest build` with TS2307 "Cannot find module
* '@apso/crud'" on every entity. Existing @nestjsx deps are left alone because
* hand-written code may still import them.
*
* @param {string} projectDir - Directory holding the project's package.json.
* @returns {string[]} Names added (empty when nothing changed or no package.json).
*/
export function ensureCrudDeps(projectDir: string): string[] {
const pkgPath = path.join(projectDir, "package.json");
if (!fs.existsSync(pkgPath)) return [];

const raw = fs.readFileSync(pkgPath, "utf8");
const pkg = JSON.parse(raw);
const declared = { ...pkg.devDependencies, ...pkg.dependencies };
const missing = Object.keys(APSO_CRUD_DEPS).filter((name) => !declared[name]);
if (missing.length === 0) return [];

pkg.dependencies = pkg.dependencies ?? {};
for (const name of missing) pkg.dependencies[name] = APSO_CRUD_DEPS[name];
pkg.dependencies = Object.fromEntries(
Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b))
);
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + (raw.endsWith("\n") ? "\n" : ""));
return missing;
}
Loading
Loading