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
1 change: 1 addition & 0 deletions app/snippets/ModelContent.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
{{hasManyRelationships}}
{{hasOneRelationships}}
{{validations}}
{{enums}}
}

}
12 changes: 12 additions & 0 deletions cli/lucli/Module.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -2794,6 +2794,10 @@ component extends="modules.BaseModule" {
var viewResult = codegen.generateView(name = controllerName, action = action);
if (viewResult.success) {
printCreated("app/views/#lCase(controllerName)#/#lCase(action)#.cfm");
} else {
// Warn instead of silently skipping — a controller reporting
// success with no views written is misleading. CLI audit M3.
out(" skip app/views/#lCase(controllerName)#/#lCase(action)#.cfm: " & (viewResult.error ?: "generation failed"), "yellow");
}
}
}
Expand Down Expand Up @@ -2935,6 +2939,14 @@ component extends="modules.BaseModule" {
out("Route already exists: #resourceRoute#", "yellow");
return "";
}
// Also detect the named-arg form (e.g. .resources(name="posts", only="...")),
// which updateRoutes() treats as a duplicate. Without this, an existing
// named-arg route was misreported as "Could not find insertion point". M5.
var namedArgPattern = "\.resources\s*\([^)]*name\s*=\s*[""']" & routeName & "[""']";
if (reFindNoCase(namedArgPattern, content)) {
out("Route already exists: .resources(name=""#routeName#"", ...)", "yellow");
return "";
}

// Delegate to Scaffold service for the actual route insertion
var scaffold = getService("scaffold");
Expand Down
20 changes: 20 additions & 0 deletions cli/lucli/services/CodeGen.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ component {
hasMany: arguments.hasMany,
hasOne: arguments.hasOne,
validations: buildModelValidations(arguments.properties),
enums: buildModelEnums(arguments.properties),
timestamp: dateTimeFormat(now(), "yyyy-mm-dd HH:nn:ss")
};

Expand Down Expand Up @@ -91,6 +92,25 @@ component {
return arrayToList(lines, chr(10) & chr(9) & chr(9));
}

/**
* Build enum() declarations for any `name:enum:a,b,c` properties. Emits one
* `enum(property="name", values="a,b,c")` line per enum property so generated
* models carry the auto-checkers/scopes the framework derives from enum().
* Previously the enum type was parsed but never emitted. CLI audit M2.
*/
private string function buildModelEnums(required array properties) {
var lines = [];
for (var prop in arguments.properties) {
var propType = structKeyExists(prop, "type") ? lCase(prop.type) : "";
if (propType == "enum" && structKeyExists(prop, "values") && len(prop.values)) {
arrayAppend(lines, 'enum(property="#prop.name#", values="#prop.values#");');
}
}
// Same newline + 2-tab join as buildModelValidations to align with the
// template's `\t\t{{enums}}` placeholder indent.
return arrayToList(lines, chr(10) & chr(9) & chr(9));
}

/**
* Generate a controller file
*/
Expand Down
11 changes: 6 additions & 5 deletions cli/lucli/services/ReleaseChannel.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ component {
public string function classify(required string moduleVersion) {
var v = trim(arguments.moduleVersion);

// Assemble the build-token sentinel at runtime so the release build's
// `@build.version@` -> <version> token replacer can't clobber this string
// literal. If written literally, the replacer turns `v == "@build.version@"`
// into `v == "4.0.2"`, so a stable build matches its own version here and
// misclassifies itself as a dev checkout. See CLI audit H10.
// Assemble the sentinel at runtime ("@" & "build.version" & "@") so the
// release build's version-token replacer can't clobber this string literal.
// If the sentinel were written as the bare literal, the replacer would swap
// it for the real version at build time — a stable build would then compare
// v against its own version here and misclassify itself as a dev checkout.
// (The comment itself avoids the bare token for the same reason.) CLI audit H10.
var devToken = "@" & "build.version" & "@";

// Empty / placeholder / dev-checkout sentinels.
Expand Down
5 changes: 5 additions & 0 deletions cli/lucli/services/Scaffold.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ component {
if (viewResult.success) {
arrayAppend(results.generated, {type: "view", path: viewResult.path});
arrayAppend(results.rollback, viewResult.path);
} else {
// Surface the failure instead of silently producing a
// "complete" scaffold with no views (e.g. unbundled
// templates, #1944). CLI audit M3.
arrayAppend(results.skipped, "view " & action & ": " & (viewResult.error ?: "generation failed"));
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions cli/lucli/services/Templates.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ component {
}
processed = reReplace(processed, "\{\{validations\}\}", validationCode, "all");

// Process {{enums}} placeholder. Mirrors {{validations}}: CodeGen.generateModel
// pre-builds an `enums` code string of enum(property=..., values=...) lines for
// any name:enum:a,b,c properties. Explicit (not just the generic {{key}} loop)
// so it's substituted even when empty. CLI audit M2.
var enumCode = (structKeyExists(arguments.context, "enums") && isSimpleValue(arguments.context.enums)) ? arguments.context.enums : "";
processed = reReplace(processed, "\{\{enums\}\}", enumCode, "all");

// Process actions for controllers
if (structKeyExists(arguments.context, "actions") && isArray(arguments.context.actions)) {
var actionsCode = generateActionsCode(arguments.context.actions);
Expand Down
1 change: 1 addition & 0 deletions cli/lucli/templates/app/app/snippets/ModelContent.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
{{hasManyRelationships}}
{{hasOneRelationships}}
{{validations}}
{{enums}}
}

}
10 changes: 10 additions & 0 deletions cli/lucli/tests/specs/deploy/DeployArgsParserSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,21 @@ component extends="wheels.wheelstest.system.BaseSpec" {
expect(opts.role).toBe("web");
});

it("parses '--role value' (space-separated) into opts.role", () => {
var opts = parser.parse(["--role", "workers"]);
expect(opts.role).toBe("workers");
});

it("parses --container into opts.container", () => {
var opts = parser.parse(["--container=app-web-v1"]);
expect(opts.container).toBe("app-web-v1");
});

it("parses '--container value' (space-separated) into opts.container", () => {
var opts = parser.parse(["--container", "app-web-v1"]);
expect(opts.container).toBe("app-web-v1");
});

it("parses --follow as a boolean flag", () => {
var opts = parser.parse(["--follow"]);
expect(opts.follow).toBeTrue();
Expand Down
17 changes: 17 additions & 0 deletions cli/lucli/tests/specs/services/CodeGenSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,23 @@ component extends="wheels.wheelstest.system.BaseSpec" {
expect(content).notToInclude("validatesFormatOf");
});

it("emits enum() for enum-typed properties (##M2)", () => {
codegen.generateModel(
name = "Ticket",
properties = [{name: "status", type: "enum", values: "open,pending,closed"}],
force = true
);
var content = fileRead(tempRoot & "/app/models/Ticket.cfc");
expect(content).toInclude('enum(property="status", values="open,pending,closed")');
});

it("leaves no stray enums placeholder when there are no enum properties (##M2)", () => {
codegen.generateModel(name = "NoEnum", properties = [{name: "title", type: "string"}], force = true);
var content = fileRead(tempRoot & "/app/models/NoEnum.cfc");
expect(content).notToInclude("{" & "{enums}}");
expect(content).notToInclude("enum(");
});

it("produces no orphan whitespace-only lines (##2329)", () => {
codegen.generateModel(
name = "Layout",
Expand Down
1 change: 1 addition & 0 deletions cli/src/templates/ModelContent.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
{{hasManyRelationships}}
{{hasOneRelationships}}
{{validations}}
{{enums}}
}

}
Loading