@cedarjs/cli 6.0.0-canary.2867 → 6.0.0-canary.2869

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.
@@ -19,7 +19,6 @@ import {
19
19
  writeFile,
20
20
  getDefaultArgs,
21
21
  getPaths,
22
- writeFilesTask,
23
22
  addRoutesToRouterTask,
24
23
  addScaffoldImport,
25
24
  transformTSToJS,
@@ -37,6 +36,7 @@ import {
37
36
  } from "../helpers.js";
38
37
  import { builder as sdlBuilder } from "../sdl/sdl.js";
39
38
  import { files as sdlFiles } from "../sdl/sdlHandler.js";
39
+ import { writeFilesWithStubsTask } from "../sdl/stubFiles.js";
40
40
  import { builder as serviceBuilder } from "../service/service.js";
41
41
  import { files as serviceFiles } from "../service/serviceHandler.js";
42
42
  import { customOrDefaultTemplatePath } from "../yargsHandlerHelpers.js";
@@ -638,7 +638,7 @@ const tasks = ({
638
638
  tailwind,
639
639
  force
640
640
  });
641
- return writeFilesTask(f, { overwriteExisting: force });
641
+ return writeFilesWithStubsTask(f, { overwriteExisting: force });
642
642
  }
643
643
  },
644
644
  {
@@ -7,7 +7,7 @@ import { generate as generateTypes } from "@cedarjs/internal/dist/generate/gener
7
7
  import { getConfig } from "@cedarjs/project-config";
8
8
  import { errorTelemetry } from "@cedarjs/telemetry";
9
9
  import { pluralize } from "@cedarjs/utils/cedarPluralize";
10
- import { transformTSToJS, writeFilesTask } from "../../../lib/index.js";
10
+ import { transformTSToJS } from "../../../lib/index.js";
11
11
  import {
12
12
  prepareForRollback,
13
13
  addFunctionToRollback
@@ -20,6 +20,11 @@ import {
20
20
  import { relationsForModel } from "../helpers.js";
21
21
  import { files as serviceFiles } from "../service/serviceHandler.js";
22
22
  import { templateForFile } from "../yargsHandlerHelpers.js";
23
+ import {
24
+ addStubHeader,
25
+ missingRelatedModels,
26
+ writeFilesWithStubsTask
27
+ } from "./stubFiles.js";
23
28
  const DEFAULT_IGNORE_FIELDS_FOR_INPUT = ["createdAt", "updatedAt"];
24
29
  const missingIdConsoleMessage = () => {
25
30
  const line1 = ansis.bold.yellow("WARNING") + ": Cannot generate CRUD SDL without an `@id` database column.";
@@ -67,12 +72,12 @@ const querySDL = (model, docs = false) => {
67
72
  return model.fields.map((field) => modelFieldToSDL({ field, docs }));
68
73
  };
69
74
  const inputSDL = (model, required, docs = false) => {
70
- const ignoredFields = DEFAULT_IGNORE_FIELDS_FOR_INPUT;
75
+ const ignoredFields = [...DEFAULT_IGNORE_FIELDS_FOR_INPUT];
76
+ const idField = model.fields.find((field) => field.isId);
77
+ if (idField?.default !== void 0) {
78
+ ignoredFields.push(idField.name);
79
+ }
71
80
  return model.fields.filter((field) => {
72
- const idField = model.fields.find((field2) => field2.isId);
73
- if (idField?.default) {
74
- ignoredFields.push(idField.name);
75
- }
76
81
  return !ignoredFields.includes(field.name) && field.kind !== "object";
77
82
  }).map((field) => modelFieldToSDL({ field, required, docs }));
78
83
  };
@@ -174,6 +179,26 @@ const files = async ({
174
179
  })
175
180
  };
176
181
  };
182
+ const stubFiles = async (models, generatedFor, { docs = false, typescript }) => {
183
+ const generatedFiles = {};
184
+ for (const stubModel of models) {
185
+ const stubModelFiles = await files({
186
+ name: stubModel,
187
+ crud: false,
188
+ docs,
189
+ tests: false,
190
+ typescript
191
+ });
192
+ for (const [stubPath, stubContent] of Object.entries(stubModelFiles)) {
193
+ generatedFiles[stubPath] = addStubHeader({
194
+ content: stubContent,
195
+ stubModel,
196
+ generatedFor
197
+ });
198
+ }
199
+ }
200
+ return generatedFiles;
201
+ };
177
202
  const handler = async ({
178
203
  model,
179
204
  crud,
@@ -197,13 +222,17 @@ const handler = async ({
197
222
  });
198
223
  try {
199
224
  const { name } = await verifyModelName({ name: model });
225
+ const missingModels = await missingRelatedModels(name);
200
226
  const tasks = new Listr(
201
227
  [
202
228
  {
203
229
  title: "Generating SDL files...",
204
230
  task: async () => {
205
- const f = await files({ name, tests, crud, typescript, docs });
206
- return writeFilesTask(f, { overwriteExisting: force });
231
+ const f = {
232
+ ...await files({ name, tests, crud, typescript, docs }),
233
+ ...await stubFiles(missingModels, name, { typescript, docs })
234
+ };
235
+ return writeFilesWithStubsTask(f, { overwriteExisting: force });
207
236
  }
208
237
  },
209
238
  {
@@ -230,6 +259,23 @@ const handler = async ({
230
259
  prepareForRollback(tasks);
231
260
  }
232
261
  await tasks.run();
262
+ if (missingModels.length > 0) {
263
+ console.log();
264
+ console.log(
265
+ c.info(
266
+ `${name} has relations to models that don't have SDL files of their own yet: ${missingModels.join(", ")}`
267
+ )
268
+ );
269
+ console.log(
270
+ c.info(
271
+ "Read-only SDL stubs were generated for them, since GraphQL type generation fails otherwise."
272
+ )
273
+ );
274
+ console.log(c.info("To replace a stub with a full SDL and service, run"));
275
+ for (const stubModel of missingModels) {
276
+ console.log(c.info(` yarn cedar generate sdl ${stubModel}`));
277
+ }
278
+ }
233
279
  } catch (e) {
234
280
  const message = e instanceof Error ? e.message : String(e);
235
281
  const exitCode = e instanceof Error && "exitCode" in e && typeof e.exitCode === "number" ? e.exitCode : 1;
@@ -240,5 +286,6 @@ const handler = async ({
240
286
  };
241
287
  export {
242
288
  files,
243
- handler
289
+ handler,
290
+ stubFiles
244
291
  };
@@ -0,0 +1,132 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { Listr } from "listr2";
5
+ import { getPaths } from "@cedarjs/project-config";
6
+ import { writeFile } from "../../../lib/index.js";
7
+ import { getSchema } from "../../../lib/schemaHelpers.js";
8
+ const STUB_HASH_MARKER = "@cedar-generator-stub-hash";
9
+ const STUB_HASH_MARKER_REGEX = new RegExp(
10
+ `^// ${STUB_HASH_MARKER} ([0-9a-f]+)$`,
11
+ "m"
12
+ );
13
+ function stubHash(contents) {
14
+ return crypto.createHash("sha256").update(contents).digest("hex").slice(0, 16);
15
+ }
16
+ function addStubHeader({
17
+ content,
18
+ stubModel,
19
+ generatedFor
20
+ }) {
21
+ const body = "\n\n" + content;
22
+ const marker = `// ${STUB_HASH_MARKER} PLACEHOLDER`;
23
+ const header = [
24
+ `// Generated as a read-only stub by \`cedar generate sdl ${generatedFor}\`,`,
25
+ `// because ${generatedFor} has a relation to ${stubModel}, which had no SDL yet.`,
26
+ `// Run \`cedar generate sdl ${stubModel}\` to replace this stub with the real thing.`,
27
+ `// If you edit this file, the hash below will stop matching and you'll`,
28
+ `// need to pass \`--force\` to overwrite it.`,
29
+ marker
30
+ ].join("\n");
31
+ const contentToHash = header + body;
32
+ const hash = stubHash(contentToHash);
33
+ const finalMarker = `// ${STUB_HASH_MARKER} ${hash}`;
34
+ return (header + body).replace(marker, finalMarker);
35
+ }
36
+ function isPristineStub(contents) {
37
+ const match = STUB_HASH_MARKER_REGEX.exec(contents);
38
+ if (!match) {
39
+ return false;
40
+ }
41
+ const contentToHash = contents.replace(
42
+ match[0],
43
+ `// ${STUB_HASH_MARKER} PLACEHOLDER`
44
+ );
45
+ return stubHash(contentToHash) === match[1];
46
+ }
47
+ function readExistingSdlFiles() {
48
+ const graphqlDir = getPaths().api.graphql;
49
+ if (!fs.existsSync(graphqlDir)) {
50
+ return [];
51
+ }
52
+ const contents = [];
53
+ const dirsToWalk = [graphqlDir];
54
+ while (dirsToWalk.length > 0) {
55
+ const dir = dirsToWalk.shift();
56
+ if (!dir) {
57
+ break;
58
+ }
59
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
60
+ const entryPath = path.join(dir, entry.name);
61
+ if (entry.isDirectory()) {
62
+ dirsToWalk.push(entryPath);
63
+ } else if (/\.sdl\.(js|ts)$/.test(entry.name)) {
64
+ contents.push(fs.readFileSync(entryPath, "utf-8"));
65
+ }
66
+ }
67
+ }
68
+ return contents;
69
+ }
70
+ async function missingRelatedModels(modelName) {
71
+ const existingSdls = readExistingSdlFiles();
72
+ const isDefined = (typeName) => existingSdls.some(
73
+ (sdl) => new RegExp(`\\btype\\s+${typeName}\\b`).test(sdl)
74
+ );
75
+ const seen = /* @__PURE__ */ new Set([modelName]);
76
+ const missing = [];
77
+ const queue = [modelName];
78
+ while (queue.length > 0) {
79
+ const current = queue.shift();
80
+ if (!current) {
81
+ break;
82
+ }
83
+ const model = await getSchema(current);
84
+ if (!model || !("fields" in model)) {
85
+ continue;
86
+ }
87
+ for (const field of model.fields) {
88
+ if (!field.relationName || seen.has(field.type)) {
89
+ continue;
90
+ }
91
+ seen.add(field.type);
92
+ queue.push(field.type);
93
+ if (!isDefined(field.type)) {
94
+ missing.push(field.type);
95
+ }
96
+ }
97
+ }
98
+ return missing;
99
+ }
100
+ function writeFilesWithStubsTask(files, { overwriteExisting = false } = {}) {
101
+ const { base } = getPaths();
102
+ return new Listr(
103
+ Object.entries(files).map(([file, contents]) => ({
104
+ title: `...waiting to write file \`./${path.relative(base, file)}\`...`,
105
+ task: (_ctx, task) => {
106
+ let canOverwrite = overwriteExisting;
107
+ if (!canOverwrite && fs.existsSync(file)) {
108
+ const existingContents = fs.readFileSync(file, "utf-8");
109
+ if (isPristineStub(existingContents)) {
110
+ canOverwrite = true;
111
+ } else if (STUB_HASH_MARKER_REGEX.test(existingContents)) {
112
+ throw new Error(
113
+ `${file} started out as a generated stub, but has since been edited. Use \`--force\` to overwrite it.`
114
+ );
115
+ }
116
+ }
117
+ return writeFile(
118
+ file,
119
+ contents,
120
+ { overwriteExisting: canOverwrite },
121
+ task
122
+ );
123
+ }
124
+ }))
125
+ );
126
+ }
127
+ export {
128
+ addStubHeader,
129
+ isPristineStub,
130
+ missingRelatedModels,
131
+ writeFilesWithStubsTask
132
+ };
@@ -30,6 +30,7 @@ export const schema = gql`
30
30
  <% } %>${singularCamelName}(${idName}: ${idType}!): ${singularPascalName} @requireAuth<% } %>
31
31
  }
32
32
 
33
+ <% if (crud) { %>
33
34
  <% if (docs) { %>
34
35
  """Autogenerated input type of Input${singularPascalName}."""
35
36
  <% } %>
@@ -51,7 +52,7 @@ export const schema = gql`
51
52
  <% } %>
52
53
  input Update${singularPascalName}Input {
53
54
  ${updateInput}
54
- }<% if (crud) { %>
55
+ }
55
56
 
56
57
  <% if (docs) { %>
57
58
  """About mutations"""
@@ -30,6 +30,7 @@ export const schema = gql`
30
30
  <% } %>${singularCamelName}(${idName}: ${idType}!): ${singularPascalName} @requireAuth<% } %>
31
31
  }
32
32
 
33
+ <% if (crud) { %>
33
34
  <% if (docs) { %>
34
35
  """Autogenerated input type of Input${singularPascalName}."""
35
36
  <% } %>
@@ -51,7 +52,7 @@ export const schema = gql`
51
52
  <% } %>
52
53
  input Update${singularPascalName}Input {
53
54
  ${updateInput}
54
- }<% if (crud) { %>
55
+ }
55
56
 
56
57
  <% if (docs) { %>
57
58
  """About mutations"""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli",
3
- "version": "6.0.0-canary.2867",
3
+ "version": "6.0.0-canary.2869",
4
4
  "description": "The CedarJS Command Line",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,17 +36,17 @@
36
36
  "@babel/preset-typescript": "7.29.7",
37
37
  "@babel/traverse": "7.29.7",
38
38
  "@babel/types": "7.29.7",
39
- "@cedarjs/api-server": "6.0.0-canary.2867",
40
- "@cedarjs/babel-config": "6.0.0-canary.2867",
41
- "@cedarjs/cli-helpers": "6.0.0-canary.2867",
42
- "@cedarjs/internal": "6.0.0-canary.2867",
43
- "@cedarjs/prerender": "6.0.0-canary.2867",
44
- "@cedarjs/project-config": "6.0.0-canary.2867",
45
- "@cedarjs/structure": "6.0.0-canary.2867",
46
- "@cedarjs/telemetry": "6.0.0-canary.2867",
47
- "@cedarjs/utils": "6.0.0-canary.2867",
48
- "@cedarjs/vite": "6.0.0-canary.2867",
49
- "@cedarjs/web-server": "6.0.0-canary.2867",
39
+ "@cedarjs/api-server": "6.0.0-canary.2869",
40
+ "@cedarjs/babel-config": "6.0.0-canary.2869",
41
+ "@cedarjs/cli-helpers": "6.0.0-canary.2869",
42
+ "@cedarjs/internal": "6.0.0-canary.2869",
43
+ "@cedarjs/prerender": "6.0.0-canary.2869",
44
+ "@cedarjs/project-config": "6.0.0-canary.2869",
45
+ "@cedarjs/structure": "6.0.0-canary.2869",
46
+ "@cedarjs/telemetry": "6.0.0-canary.2869",
47
+ "@cedarjs/utils": "6.0.0-canary.2869",
48
+ "@cedarjs/vite": "6.0.0-canary.2869",
49
+ "@cedarjs/web-server": "6.0.0-canary.2869",
50
50
  "@listr2/prompt-adapter-enquirer": "4.3.0",
51
51
  "@opentelemetry/api": "1.9.1",
52
52
  "@opentelemetry/core": "1.30.1",
@@ -94,7 +94,7 @@
94
94
  "yargs": "17.7.3"
95
95
  },
96
96
  "devDependencies": {
97
- "@cedarjs/framework-tools": "6.0.0-canary.2867",
97
+ "@cedarjs/framework-tools": "6.0.0-canary.2869",
98
98
  "@prisma/dmmf": "7.8.0",
99
99
  "@types/archiver": "^7.0.0",
100
100
  "memfs": "4.64.0",