@nakedev/go-scaffold 0.1.0

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.
Files changed (73) hide show
  1. package/README.md +223 -0
  2. package/bin/go-scaffold.js +2 -0
  3. package/dist/commands/create.js +57 -0
  4. package/dist/commands/generate.js +97 -0
  5. package/dist/commands/method.js +70 -0
  6. package/dist/commands/remove.js +72 -0
  7. package/dist/index.js +138 -0
  8. package/dist/prompts/create-wizard.js +43 -0
  9. package/dist/prompts/generate-wizard.js +68 -0
  10. package/dist/templates/create-manifest.js +110 -0
  11. package/dist/templates/module-manifest.js +28 -0
  12. package/dist/types.js +2 -0
  13. package/dist/utils/config.js +53 -0
  14. package/dist/utils/main-patcher.js +59 -0
  15. package/dist/utils/marker-patch.js +63 -0
  16. package/dist/utils/method-patcher.js +271 -0
  17. package/dist/utils/migrations.js +17 -0
  18. package/dist/utils/module-paths.js +33 -0
  19. package/dist/utils/naming.js +160 -0
  20. package/dist/utils/openapi-patcher.js +47 -0
  21. package/dist/utils/template-renderer.js +51 -0
  22. package/package.json +49 -0
  23. package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +83 -0
  24. package/templates/create/base/.env.example.hbs +7 -0
  25. package/templates/create/base/.github/workflows/ci.yml.hbs +46 -0
  26. package/templates/create/base/.gitignore.hbs +5 -0
  27. package/templates/create/base/.golangci.yml.hbs +32 -0
  28. package/templates/create/base/.vscode/settings.json.hbs +11 -0
  29. package/templates/create/base/AGENTS.md.hbs +68 -0
  30. package/templates/create/base/CLAUDE.md.hbs +1 -0
  31. package/templates/create/base/Makefile.hbs +93 -0
  32. package/templates/create/base/README.md.hbs +143 -0
  33. package/templates/create/base/cmd/api/main.go.hbs +116 -0
  34. package/templates/create/base/go.mod.hbs +11 -0
  35. package/templates/create/base/internal/platform/database/database.go.hbs +28 -0
  36. package/templates/create/base/internal/shared/apperror/apperror.go.hbs +36 -0
  37. package/templates/create/base/internal/shared/config/config.go.hbs +46 -0
  38. package/templates/create/base/internal/shared/dberr/dberr.go.hbs +28 -0
  39. package/templates/create/base/internal/shared/httpx/httpx.go.hbs +37 -0
  40. package/templates/create/base/internal/shared/id/id.go.hbs +16 -0
  41. package/templates/create/base/internal/shared/middleware/error.go.hbs +33 -0
  42. package/templates/create/base/internal/shared/middleware/logger.go.hbs +23 -0
  43. package/templates/create/base/internal/shared/middleware/requestid.go.hbs +36 -0
  44. package/templates/create/base/internal/shared/pagination/pagination.go.hbs +39 -0
  45. package/templates/create/base/migrations/.gitkeep.hbs +0 -0
  46. package/templates/create/features/docker-compose.yml.hbs +14 -0
  47. package/templates/create/features/docs/architecture.md.hbs +99 -0
  48. package/templates/create/features/docs/common/parameters.yaml.hbs +13 -0
  49. package/templates/create/features/docs/common/responses.yaml.hbs +20 -0
  50. package/templates/create/features/docs/common/schemas.yaml.hbs +23 -0
  51. package/templates/create/features/docs/health/health-livez.yaml.hbs +13 -0
  52. package/templates/create/features/docs/health/health-readyz.yaml.hbs +21 -0
  53. package/templates/create/features/docs/openapi.yaml.hbs +33 -0
  54. package/templates/create/features/docs/patterns.md.hbs +119 -0
  55. package/templates/create/features/docs/techstack.md.hbs +38 -0
  56. package/templates/generate/module/docs/collection.yaml.hbs +36 -0
  57. package/templates/generate/module/docs/item.yaml.hbs +37 -0
  58. package/templates/generate/module/docs/schemas.yaml.hbs +13 -0
  59. package/templates/generate/module/dto.go.hbs +29 -0
  60. package/templates/generate/module/errors.go.hbs +28 -0
  61. package/templates/generate/module/handler.go.hbs +103 -0
  62. package/templates/generate/module/handler_test.go.hbs +105 -0
  63. package/templates/generate/module/migration.down.sql.hbs +1 -0
  64. package/templates/generate/module/migration.up.sql.hbs +5 -0
  65. package/templates/generate/module/minimal/dto.go.hbs +26 -0
  66. package/templates/generate/module/minimal/handler.go.hbs +24 -0
  67. package/templates/generate/module/minimal/handler_test.go.hbs +70 -0
  68. package/templates/generate/module/minimal/service.go.hbs +45 -0
  69. package/templates/generate/module/minimal/service_test.go.hbs +54 -0
  70. package/templates/generate/module/model/model.go.hbs +20 -0
  71. package/templates/generate/module/repository.go.hbs +49 -0
  72. package/templates/generate/module/service.go.hbs +97 -0
  73. package/templates/generate/module/service_test.go.hbs +65 -0
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promptProjectName = promptProjectName;
4
+ exports.runCreateWizard = runCreateWizard;
5
+ const prompts_1 = require("@inquirer/prompts");
6
+ const naming_1 = require("../utils/naming");
7
+ async function promptProjectName() {
8
+ const name = await (0, prompts_1.input)({
9
+ message: "Project name:",
10
+ validate: (value) => {
11
+ if (!value.trim())
12
+ return "project name is required";
13
+ return (0, naming_1.validateGoModulePath)(value.trim());
14
+ },
15
+ });
16
+ return name.trim();
17
+ }
18
+ async function runCreateWizard() {
19
+ console.log("\nConfigure your project:\n");
20
+ const docker = await (0, prompts_1.confirm)({
21
+ message: "Include Docker Compose (local Postgres)?",
22
+ default: true,
23
+ });
24
+ const openapiDocs = await (0, prompts_1.confirm)({
25
+ message: "Include hand-written OpenAPI docs (docs/openapi.yaml, whole docs/ tree served at /docs)?",
26
+ default: true,
27
+ });
28
+ const apiPrefixRaw = await (0, prompts_1.input)({
29
+ message: "API route prefix (e.g. v1, api/v1; leave blank for none):",
30
+ default: "v1",
31
+ validate: naming_1.validateApiPrefix,
32
+ });
33
+ const apiPrefix = (0, naming_1.normalizeApiPrefix)(apiPrefixRaw);
34
+ console.log("\nSummary:");
35
+ console.log(` Docker + PostgreSQL: ${docker ? "yes" : "no"}`);
36
+ console.log(` OpenAPI docs: ${openapiDocs ? "yes" : "no"}`);
37
+ console.log(` Route prefix: ${apiPrefix ? `/${apiPrefix}` : "(none)"}`);
38
+ const proceed = await (0, prompts_1.confirm)({ message: "\nCreate project with these settings?", default: true });
39
+ if (!proceed) {
40
+ throw new Error("project creation cancelled");
41
+ }
42
+ return { features: { docker, openapiDocs }, apiPrefix };
43
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promptModuleName = promptModuleName;
4
+ exports.promptMethodName = promptMethodName;
5
+ exports.promptMethodType = promptMethodType;
6
+ exports.promptGetMode = promptGetMode;
7
+ exports.promptLookupField = promptLookupField;
8
+ const prompts_1 = require("@inquirer/prompts");
9
+ const naming_1 = require("../utils/naming");
10
+ // wraps an assert-style validator into inquirer's true|string contract so a
11
+ // reserved word re-prompts inline instead of aborting the whole command.
12
+ function notKeyword(value, role) {
13
+ try {
14
+ (0, naming_1.assertNotGoKeyword)((0, naming_1.toCamelCase)(value.trim()), role);
15
+ return true;
16
+ }
17
+ catch (e) {
18
+ return e.message;
19
+ }
20
+ }
21
+ async function promptModuleName() {
22
+ const name = await (0, prompts_1.input)({
23
+ message: "Module name (singular, e.g. order, product):",
24
+ validate: (value) => (value.trim() ? (0, naming_1.validateModuleName)(value) : "module name is required"),
25
+ });
26
+ return name.trim();
27
+ }
28
+ async function promptMethodName() {
29
+ const name = await (0, prompts_1.input)({
30
+ message: "Method name (e.g. approve, findByStatus, resetPassword):",
31
+ validate: (value) => (value.trim() ? notKeyword(value, "method") : "method name is required"),
32
+ });
33
+ return name.trim();
34
+ }
35
+ async function promptMethodType() {
36
+ return (0, prompts_1.select)({
37
+ message: "Method type:",
38
+ choices: [
39
+ { name: "GET", value: "get" },
40
+ { name: "POST", value: "post" },
41
+ { name: "PUT", value: "put" },
42
+ { name: "PATCH", value: "patch" },
43
+ { name: "DELETE", value: "delete" },
44
+ ],
45
+ });
46
+ }
47
+ async function promptGetMode() {
48
+ return (0, prompts_1.select)({
49
+ message: "GET mode:",
50
+ choices: [
51
+ { name: "List (all) — a new list endpoint with its own filter", value: "all" },
52
+ { name: "Single record lookup (one) — find by a field other than id", value: "one" },
53
+ ],
54
+ });
55
+ }
56
+ async function promptLookupField() {
57
+ const field = await (0, prompts_1.input)({
58
+ message: "Lookup field (e.g. email, status, slug):",
59
+ validate: (value) => {
60
+ if (!value.trim())
61
+ return "field is required";
62
+ if (value.trim().toLowerCase() === "id")
63
+ return '"id" already has a lookup route — pick another field';
64
+ return notKeyword(value, "lookup field");
65
+ },
66
+ });
67
+ return field.trim();
68
+ }
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CREATE_MANIFEST = void 0;
4
+ exports.CREATE_MANIFEST = [
5
+ { template: "create/base/go.mod.hbs", output: "go.mod" },
6
+ { template: "create/base/.gitignore.hbs", output: ".gitignore" },
7
+ { template: "create/base/.env.example.hbs", output: ".env.example" },
8
+ { template: "create/base/Makefile.hbs", output: "Makefile" },
9
+ { template: "create/base/.golangci.yml.hbs", output: ".golangci.yml" },
10
+ { template: "create/base/.vscode/settings.json.hbs", output: ".vscode/settings.json" },
11
+ { template: "create/base/.github/workflows/ci.yml.hbs", output: ".github/workflows/ci.yml" },
12
+ { template: "create/base/README.md.hbs", output: "README.md" },
13
+ { template: "create/base/AGENTS.md.hbs", output: "AGENTS.md" },
14
+ { template: "create/base/CLAUDE.md.hbs", output: "CLAUDE.md" },
15
+ {
16
+ template: "create/base/.claude/skills/go-scaffold/SKILL.md.hbs",
17
+ output: ".claude/skills/go-scaffold/SKILL.md",
18
+ },
19
+ { template: "create/base/cmd/api/main.go.hbs", output: "cmd/api/main.go" },
20
+ {
21
+ template: "create/base/internal/platform/database/database.go.hbs",
22
+ output: "internal/platform/database/database.go",
23
+ },
24
+ {
25
+ template: "create/base/internal/shared/config/config.go.hbs",
26
+ output: "internal/shared/config/config.go",
27
+ },
28
+ {
29
+ template: "create/base/internal/shared/apperror/apperror.go.hbs",
30
+ output: "internal/shared/apperror/apperror.go",
31
+ },
32
+ {
33
+ template: "create/base/internal/shared/dberr/dberr.go.hbs",
34
+ output: "internal/shared/dberr/dberr.go",
35
+ },
36
+ {
37
+ template: "create/base/internal/shared/httpx/httpx.go.hbs",
38
+ output: "internal/shared/httpx/httpx.go",
39
+ },
40
+ {
41
+ template: "create/base/internal/shared/id/id.go.hbs",
42
+ output: "internal/shared/id/id.go",
43
+ },
44
+ {
45
+ template: "create/base/internal/shared/pagination/pagination.go.hbs",
46
+ output: "internal/shared/pagination/pagination.go",
47
+ },
48
+ {
49
+ template: "create/base/internal/shared/middleware/error.go.hbs",
50
+ output: "internal/shared/middleware/error.go",
51
+ },
52
+ {
53
+ template: "create/base/internal/shared/middleware/logger.go.hbs",
54
+ output: "internal/shared/middleware/logger.go",
55
+ },
56
+ {
57
+ template: "create/base/internal/shared/middleware/requestid.go.hbs",
58
+ output: "internal/shared/middleware/requestid.go",
59
+ },
60
+ { template: "create/base/migrations/.gitkeep.hbs", output: "migrations/.gitkeep" },
61
+ // architecture standards docs — always included, this is the point of the CLI
62
+ {
63
+ template: "create/features/docs/architecture.md.hbs",
64
+ output: "docs/architect/architecture.md",
65
+ },
66
+ {
67
+ template: "create/features/docs/patterns.md.hbs",
68
+ output: "docs/architect/patterns.md",
69
+ },
70
+ {
71
+ template: "create/features/docs/techstack.md.hbs",
72
+ output: "docs/architect/techstack.md",
73
+ },
74
+ // opt-in features
75
+ {
76
+ template: "create/features/docker-compose.yml.hbs",
77
+ output: "docker-compose.yml",
78
+ when: (ctx) => ctx.docker,
79
+ },
80
+ {
81
+ template: "create/features/docs/openapi.yaml.hbs",
82
+ output: "docs/openapi.yaml",
83
+ when: (ctx) => ctx.openapiDocs,
84
+ },
85
+ {
86
+ template: "create/features/docs/common/parameters.yaml.hbs",
87
+ output: "docs/common/parameters.yaml",
88
+ when: (ctx) => ctx.openapiDocs,
89
+ },
90
+ {
91
+ template: "create/features/docs/common/responses.yaml.hbs",
92
+ output: "docs/common/responses.yaml",
93
+ when: (ctx) => ctx.openapiDocs,
94
+ },
95
+ {
96
+ template: "create/features/docs/common/schemas.yaml.hbs",
97
+ output: "docs/common/schemas.yaml",
98
+ when: (ctx) => ctx.openapiDocs,
99
+ },
100
+ {
101
+ template: "create/features/docs/health/health-livez.yaml.hbs",
102
+ output: "docs/health/health-livez.yaml",
103
+ when: (ctx) => ctx.openapiDocs,
104
+ },
105
+ {
106
+ template: "create/features/docs/health/health-readyz.yaml.hbs",
107
+ output: "docs/health/health-readyz.yaml",
108
+ when: (ctx) => ctx.openapiDocs,
109
+ },
110
+ ];
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MODULE_FILES_MINIMAL = exports.MODULE_FILES = void 0;
4
+ // output paths are relative to the module's own directory
5
+ // (internal/app/<pkg> or internal/app/v<version>/<pkg>)
6
+ exports.MODULE_FILES = [
7
+ { template: "generate/module/model/model.go.hbs", output: "model/model.go" },
8
+ { template: "generate/module/dto.go.hbs", output: "dto.go" },
9
+ { template: "generate/module/errors.go.hbs", output: "errors.go" },
10
+ { template: "generate/module/repository.go.hbs", output: "repository.go" },
11
+ { template: "generate/module/service.go.hbs", output: "service.go" },
12
+ { template: "generate/module/handler.go.hbs", output: "handler.go" },
13
+ { template: "generate/module/service_test.go.hbs", output: "service_test.go" },
14
+ { template: "generate/module/handler_test.go.hbs", output: "handler_test.go" },
15
+ ];
16
+ // minimal: same model/errors/repository (generate method's patches assume the
17
+ // full data-access surface exists), but no default CRUD in dto/service/handler
18
+ // — add endpoints one at a time with `generate method`.
19
+ exports.MODULE_FILES_MINIMAL = [
20
+ { template: "generate/module/model/model.go.hbs", output: "model/model.go" },
21
+ { template: "generate/module/minimal/dto.go.hbs", output: "dto.go" },
22
+ { template: "generate/module/errors.go.hbs", output: "errors.go" },
23
+ { template: "generate/module/repository.go.hbs", output: "repository.go" },
24
+ { template: "generate/module/minimal/service.go.hbs", output: "service.go" },
25
+ { template: "generate/module/minimal/handler.go.hbs", output: "handler.go" },
26
+ { template: "generate/module/minimal/service_test.go.hbs", output: "service_test.go" },
27
+ { template: "generate/module/minimal/handler_test.go.hbs", output: "handler_test.go" },
28
+ ];
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.configPath = configPath;
7
+ exports.writeConfig = writeConfig;
8
+ exports.readConfig = readConfig;
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const CONFIG_FILE = "go-scaffold.config.json";
12
+ function configPath(projectDir) {
13
+ return path_1.default.join(projectDir, CONFIG_FILE);
14
+ }
15
+ function writeConfig(projectDir, config) {
16
+ fs_extra_1.default.writeJsonSync(configPath(projectDir), config, { spaces: 2 });
17
+ }
18
+ // readConfig falls back to detecting from go.mod when the config file is
19
+ // missing (e.g. a project scaffolded before this file existed).
20
+ function readConfig(projectDir) {
21
+ const file = configPath(projectDir);
22
+ if (fs_extra_1.default.existsSync(file)) {
23
+ return fs_extra_1.default.readJsonSync(file);
24
+ }
25
+ return detectConfig(projectDir);
26
+ }
27
+ function detectConfig(projectDir) {
28
+ const goModPath = path_1.default.join(projectDir, "go.mod");
29
+ if (!fs_extra_1.default.existsSync(goModPath)) {
30
+ throw new Error(`no ${CONFIG_FILE} and no go.mod found in ${projectDir} — run this inside a go-scaffold project`);
31
+ }
32
+ const goMod = fs_extra_1.default.readFileSync(goModPath, "utf8");
33
+ const moduleMatch = goMod.match(/^module\s+(\S+)/m);
34
+ const goModule = moduleMatch ? moduleMatch[1] : path_1.default.basename(projectDir);
35
+ // parse the chosen prefix back out of `api := r.Group("/v1")` in main.go;
36
+ // an empty group (`r.Group("")`) or no match at all means no prefix.
37
+ let apiPrefix = "";
38
+ const mainGoPath = path_1.default.join(projectDir, "cmd", "api", "main.go");
39
+ if (fs_extra_1.default.existsSync(mainGoPath)) {
40
+ const groupMatch = fs_extra_1.default.readFileSync(mainGoPath, "utf8").match(/api\s*:=\s*r\.Group\("\/?([a-z0-9/]*)"\)/);
41
+ if (groupMatch)
42
+ apiPrefix = groupMatch[1];
43
+ }
44
+ return {
45
+ projectName: path_1.default.basename(projectDir),
46
+ goModule,
47
+ apiPrefix,
48
+ features: {
49
+ docker: fs_extra_1.default.existsSync(path_1.default.join(projectDir, "docker-compose.yml")),
50
+ openapiDocs: fs_extra_1.default.existsSync(path_1.default.join(projectDir, "docs", "openapi.yaml")),
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.patchMainGo = patchMainGo;
7
+ exports.unpatchMainGo = unpatchMainGo;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const marker_patch_1 = require("./marker-patch");
10
+ const IMPORT_MARKER = "// go-scaffold:imports";
11
+ const MODEL_MARKER = "// go-scaffold:models";
12
+ const ROUTE_MARKER = "// go-scaffold:routes";
13
+ // no leading tab: insertBeforeMarker re-indents, and removeLines matches by
14
+ // trimmed text — so this stays correct regardless of gofmt's indentation.
15
+ const UNUSED_API_LINE = "_ = api // dropped once `generate module` registers the first route";
16
+ // the exact lines patchMainGo inserts for a module — one source of truth so
17
+ // unpatchMainGo removes precisely what patch added.
18
+ function mainGoLines(patch) {
19
+ const modelAlias = `${patch.pkg}model`; // every domain's model subpackage is named "model"
20
+ return {
21
+ importLine: `"${patch.goModule}/internal/app/${patch.modulePath}"`,
22
+ modelImportLine: `${modelAlias} "${patch.goModule}/internal/app/${patch.modulePath}/model"`,
23
+ migrateLine: `&${modelAlias}.${patch.pascalName}{},`,
24
+ // `api` is the one route group declared by main.go.hbs, prefixed with
25
+ // whatever apiPrefix the project chose at create time (e.g. /v1, /api,
26
+ // or none) — every module registers on it, there is no per-module choice.
27
+ routeLine: `${patch.pkg}.NewHandler(${patch.pkg}.NewService(${patch.pkg}.NewRepository(db))).Register(api)`,
28
+ };
29
+ }
30
+ // patchMainGo wires a newly generated module into cmd/api/main.go: its
31
+ // import, its model in the AutoMigrate call, and its route registration —
32
+ // via marker comments rather than a Go AST rewrite (ponytail: text insertion
33
+ // at a fixed marker is enough here; reach for go/ast if main.go ever needs
34
+ // edits markers can't express).
35
+ function patchMainGo(mainGoPath, patch) {
36
+ let content = fs_extra_1.default.readFileSync(mainGoPath, "utf8");
37
+ const { importLine, modelImportLine, migrateLine, routeLine } = mainGoLines(patch);
38
+ // each guarded by its own sentinel so re-running after only the module
39
+ // folder was deleted (main.go still wired) is a no-op, not a dup that
40
+ // panics gin at startup.
41
+ content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, importLine, importLine);
42
+ content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, IMPORT_MARKER, modelImportLine, modelImportLine);
43
+ content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, MODEL_MARKER, migrateLine, migrateLine);
44
+ content = (0, marker_patch_1.insertBeforeMarkerOnce)(content, ROUTE_MARKER, routeLine, routeLine);
45
+ content = (0, marker_patch_1.removeLines)(content, [UNUSED_API_LINE]);
46
+ fs_extra_1.default.writeFileSync(mainGoPath, content);
47
+ }
48
+ // unpatchMainGo removes a module's wiring — the inverse of patchMainGo. If it
49
+ // leaves no registered routes, it restores the `_ = api` placeholder so
50
+ // main.go still compiles (api would otherwise be declared-and-unused).
51
+ function unpatchMainGo(mainGoPath, patch) {
52
+ let content = fs_extra_1.default.readFileSync(mainGoPath, "utf8");
53
+ const { importLine, modelImportLine, migrateLine, routeLine } = mainGoLines(patch);
54
+ content = (0, marker_patch_1.removeLines)(content, [importLine, modelImportLine, migrateLine, routeLine]);
55
+ if (!content.includes(".Register(api)") && !content.includes(UNUSED_API_LINE)) {
56
+ content = (0, marker_patch_1.insertBeforeMarker)(content, ROUTE_MARKER, UNUSED_API_LINE);
57
+ }
58
+ fs_extra_1.default.writeFileSync(mainGoPath, content);
59
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ // Shared text-marker patching: find a line that's exactly a marker comment,
3
+ // insert a block right above it (re-indented to match), and leave the marker
4
+ // in place so the next generate call can insert above it again.
5
+ // ponytail: text insertion at a fixed marker, not a Go AST rewrite — good
6
+ // enough for appending declarations; reach for go/ast if a patch ever needs
7
+ // to understand existing code, not just append next to it.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.hasMarker = hasMarker;
10
+ exports.ensureImport = ensureImport;
11
+ exports.insertBeforeMarkerOnce = insertBeforeMarkerOnce;
12
+ exports.removeLines = removeLines;
13
+ exports.insertBeforeMarker = insertBeforeMarker;
14
+ function hasMarker(content, marker) {
15
+ return content.split("\n").some((l) => l.trim() === marker);
16
+ }
17
+ // ensureImport adds an import line if it's not already present — needed
18
+ // because a minimal module's handler.go starts without net/http/httpx/
19
+ // pagination, and generate method's patches are the first thing to need
20
+ // them. Idempotent, so repeat calls (e.g. two generate method calls) are
21
+ // safe; doesn't attempt import grouping/sorting, just validity — run
22
+ // goimports separately if you want that tidied up.
23
+ function ensureImport(content, importPath) {
24
+ const importLine = `"${importPath}"`;
25
+ if (content.includes(importLine))
26
+ return content;
27
+ return content.replace(/import \(\n/, `import (\n\t${importLine}\n`);
28
+ }
29
+ // insertBeforeMarkerOnce: like insertBeforeMarker but a no-op if `sentinel`
30
+ // already appears in the file. Makes module wiring idempotent — re-running
31
+ // `generate module` after deleting just the module folder (leaving main.go /
32
+ // openapi.yaml still referencing it) won't duplicate the import/route/path.
33
+ // A duplicate route silently passes build+vet, then panics gin at startup
34
+ // ("handlers are already registered"), so this guard matters.
35
+ function insertBeforeMarkerOnce(content, marker, block, sentinel) {
36
+ if (content.includes(sentinel))
37
+ return content;
38
+ return insertBeforeMarker(content, marker, block);
39
+ }
40
+ // removeLines drops every line whose trimmed text exactly equals one of the
41
+ // given lines — the inverse of insertBeforeMarker for `remove module`, which
42
+ // needs to pull a module's import/route/path entries back out. Exact-trim
43
+ // match so it can't clip an unrelated line that merely contains the text.
44
+ function removeLines(content, trimmedLines) {
45
+ const drop = new Set(trimmedLines.map((l) => l.trim()));
46
+ return content
47
+ .split("\n")
48
+ .filter((l) => !drop.has(l.trim()))
49
+ .join("\n");
50
+ }
51
+ function insertBeforeMarker(content, marker, block) {
52
+ const lines = content.split("\n");
53
+ const markerLine = lines.find((l) => l.trim() === marker);
54
+ if (markerLine === undefined) {
55
+ throw new Error(`marker "${marker}" not found — the file may have been hand-edited; add the marker back or edit it by hand`);
56
+ }
57
+ const indent = markerLine.match(/^\s*/)?.[0] ?? "";
58
+ const indentedBlock = block
59
+ .split("\n")
60
+ .map((line) => (line ? `${indent}${line}` : line))
61
+ .join("\n");
62
+ return content.replace(markerLine, `${indentedBlock}\n${markerLine}`);
63
+ }