@cedarjs/cli 7.0.1-next.314 → 7.0.1-next.466

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 (59) hide show
  1. package/dist/commands/deploy/baremetal/baremetalHandler.js +375 -76
  2. package/dist/commands/deploy/baremetal/monitors.js +66 -0
  3. package/dist/commands/deploy/baremetal.js +6 -0
  4. package/dist/commands/destroy/scaffold/scaffoldHandler.js +63 -25
  5. package/dist/commands/dev/devHandler.js +13 -1
  6. package/dist/commands/generate/dbAuth/dbAuth.js +6 -2
  7. package/dist/commands/generate/dbAuth/dbAuthHandler.js +57 -14
  8. package/dist/commands/generate/dbAuth/templates/changePassword.tsx.template +131 -0
  9. package/dist/commands/generate/dbAuth/templates/login.webAuthn.tsx.template +20 -12
  10. package/dist/commands/generate/directive/directive.js +15 -2
  11. package/dist/commands/generate/helpers.js +26 -0
  12. package/dist/commands/generate/ogImage/templates/ogImage.og.tsx.template +1 -1
  13. package/dist/commands/generate/package/templates/tsconfig.json.template +1 -0
  14. package/dist/commands/generate/realtime/templates/liveQueries/blank/blank.service.ts.template +12 -4
  15. package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.service.ts.template +2 -2
  16. package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.ts.template +3 -3
  17. package/dist/commands/generate/scaffold/scaffold.js +1 -1
  18. package/dist/commands/generate/scaffold/scaffoldHandler.js +83 -44
  19. package/dist/commands/generate/scaffold/templates/assets/scaffold.tailwind.css.template +12 -7
  20. package/dist/commands/generate/scaffold/templates/components/EditNameCell.tsx.template +7 -7
  21. package/dist/commands/generate/scaffold/templates/components/NameForm.tsx.template +7 -6
  22. package/dist/commands/generate/scaffold/templates/layouts/NamesScaffoldLayout.tsx.template +18 -0
  23. package/dist/commands/generate/scaffold/templates/lib/formatters.tsx.template +3 -3
  24. package/dist/commands/generate/script/templates/tsconfig.json.template +1 -0
  25. package/dist/commands/generate/sdl/sdlHandler.js +8 -3
  26. package/dist/commands/generate/service/serviceHandler.js +32 -11
  27. package/dist/commands/generate/service/templates/service.ts.template +21 -8
  28. package/dist/commands/generate/service/templates/test.ts.template +7 -5
  29. package/dist/commands/jobsHandler.js +5 -3
  30. package/dist/commands/setup/cache/templates/memcached.ts.template +14 -17
  31. package/dist/commands/setup/cache/templates/redis.ts.template +12 -20
  32. package/dist/commands/setup/deploy/helpers/index.js +3 -1
  33. package/dist/commands/setup/deploy/providers/baremetal.js +7 -0
  34. package/dist/commands/setup/deploy/providers/baremetalHandler.js +128 -20
  35. package/dist/commands/setup/deploy/providers/netlifyHandler.js +25 -3
  36. package/dist/commands/setup/deploy/templates/baremetal.js +119 -9
  37. package/dist/commands/setup/deploy/templates/netlifyUD.js +3 -0
  38. package/dist/commands/setup/jobs/jobsHandler.js +2 -0
  39. package/dist/commands/setup/monitoring/sentry/sentryHandler.js +5 -2
  40. package/dist/commands/setup/monitoring/sentry/templates/sentryApi.ts.template +1 -4
  41. package/dist/commands/setup/monitoring/sentry/templates/sentryWeb.ts.template +7 -4
  42. package/dist/commands/setup/realtime/templates/defer/fastAndSlowFields/fastAndSlowFields.ts.template +4 -1
  43. package/dist/commands/setup/realtime/templates/liveQueries/auctions/auctions.ts.template +12 -4
  44. package/dist/commands/setup/realtime/templates/subscriptions/newMessage/newMessage.ts.template +1 -1
  45. package/dist/commands/setup/tenancy/signupCodemod.js +2 -1
  46. package/dist/commands/setup/tenancy/templates/InvitePage.tsx.template +10 -1
  47. package/dist/commands/setup/tenancy/templates/organizations.scenarios.ts.template +0 -2
  48. package/dist/commands/setup/tenancy/templates/organizations.test.ts.template +0 -8
  49. package/dist/commands/setup/tenancy/templates/organizations.ts.template +1 -1
  50. package/dist/commands/setup/tenancy/templates/requireMembership.test.ts.template +1 -1
  51. package/dist/commands/setup/tenancy/tenancyHandler.js +1 -1
  52. package/dist/commands/setup/ui/libraries/tailwindcssHandler.js +61 -81
  53. package/dist/commands/setup/uploads/gitignore.js +17 -0
  54. package/dist/commands/setup/uploads/serverFile.js +93 -27
  55. package/dist/commands/setup/uploads/templates/uploads.ts.template +14 -5
  56. package/dist/commands/setup/uploads/uploadsHandler.js +22 -1
  57. package/dist/lib/index.js +10 -6
  58. package/package.json +24 -24
  59. package/dist/commands/setup/ui/templates/postcss.config.cjs.template +0 -9
@@ -3,10 +3,7 @@ import * as Sentry from '@sentry/node'
3
3
  Sentry.init({
4
4
  dsn: process.env.SENTRY_DSN,
5
5
  environment: process.env.NODE_ENV,
6
- integrations: [
7
- Sentry.prismaIntegration(),
8
- Sentry.httpIntegration(),
9
- ],
6
+ integrations: [Sentry.prismaIntegration(), Sentry.httpIntegration()],
10
7
  tracesSampleRate: 1.0,
11
8
  })
12
9
 
@@ -1,9 +1,12 @@
1
1
  import * as Sentry from '@sentry/react'
2
2
 
3
+ const sentryDsn =
4
+ typeof process === 'undefined' ? undefined : process.env?.SENTRY_DSN
5
+
3
6
  let dsn = ''
4
7
  let environment = 'development'
5
8
 
6
- if (typeof process === 'undefined' || !process.env?.SENTRY_DSN) {
9
+ if (!sentryDsn) {
7
10
  console.error(
8
11
  'Missing SENTRY_DSN environment variable. Did you forget to add it to ' +
9
12
  'your cedar.toml (or redwood.toml) in `includeEnvironmentVariables`?'
@@ -17,14 +20,14 @@ if (typeof process === 'undefined' || !process.env?.SENTRY_DSN) {
17
20
  `)
18
21
  console.error('Sentry is disabled for now')
19
22
  } else {
20
- dsn = process.env.SENTRY_DSN
21
- environment = process.env.NODE_ENV
23
+ dsn = sentryDsn
24
+ environment = process.env.NODE_ENV ?? 'development'
22
25
  }
23
26
 
24
27
  Sentry.init({
25
28
  dsn,
26
29
  environment,
27
- integrations: [ Sentry.browserTracingIntegration() ],
30
+ integrations: [Sentry.browserTracingIntegration()],
28
31
  tracesSampleRate: 1.0,
29
32
  })
30
33
 
@@ -7,7 +7,10 @@ export const fastField = async () => {
7
7
  return 'I am fast'
8
8
  }
9
9
 
10
- export const slowField = async (_, { waitFor = 5000 }) => {
10
+ export const slowField = async (
11
+ _: unknown,
12
+ { waitFor = 5000 }: { waitFor?: number }
13
+ ) => {
11
14
  logger.debug('waiting on slowField')
12
15
  await wait(waitFor)
13
16
  return 'I am slow'
@@ -1,8 +1,16 @@
1
+ import type { BidInput } from 'types/graphql'
2
+
1
3
  import type { LiveQueryStorageMechanism } from '@cedarjs/realtime'
2
4
 
3
5
  import { logger } from 'src/lib/logger'
4
6
 
5
- const auctions = [
7
+ interface AuctionData {
8
+ id: string
9
+ title: string
10
+ bids: { amount: number }[]
11
+ }
12
+
13
+ const auctions: AuctionData[] = [
6
14
  { id: '1', title: 'RedwoodJS Logo Shirt', bids: [{ amount: 20 }] },
7
15
  { id: '2', title: 'RedwoodJS Lapel Pin', bids: [{ amount: 5 }] },
8
16
  { id: '3', title: 'RedwoodJS Beanie', bids: [{ amount: 15 }] },
@@ -34,14 +42,14 @@ const auctions = [
34
42
  * }
35
43
  * }
36
44
  */
37
- export const auction = async ({ id }) => {
45
+ export const auction = async ({ id }: { id: string }) => {
38
46
  const foundAuction = auctions.find((a) => a.id === id)
39
47
  logger.debug({ id, auction: foundAuction }, 'auction')
40
48
  return foundAuction
41
49
  }
42
50
 
43
51
  export const bid = async (
44
- { input },
52
+ { input }: { input: BidInput },
45
53
  { context }: { context: { liveQueryStore: LiveQueryStorageMechanism } }
46
54
  ) => {
47
55
  const { auctionId, amount } = input
@@ -62,7 +70,7 @@ export const bid = async (
62
70
  }
63
71
 
64
72
  export const Auction = {
65
- highestBid: (obj, { root }) => {
73
+ highestBid: (obj: unknown, { root }: { root: AuctionData }) => {
66
74
  const [max] = root.bids.sort((a, b) => b.amount - a.amount)
67
75
 
68
76
  logger.debug({ obj, root }, 'highestBid')
@@ -48,7 +48,7 @@ const newMessage = {
48
48
 
49
49
  return pubSub.subscribe('newMessage', roomId)
50
50
  },
51
- resolve: (payload) => {
51
+ resolve: (payload: NewMessageChannel['newMessage'][1]) => {
52
52
  logger.debug({ payload }, 'newMessage subscription resolve')
53
53
 
54
54
  return payload
@@ -52,7 +52,8 @@ function wrapCreateCall(fn) {
52
52
  ),
53
53
  j.objectProperty(
54
54
  j.identifier("invitationToken"),
55
- j.memberExpression(
55
+ // `userAttributes` is optional in dbAuth's signup handler options
56
+ j.optionalMemberExpression(
56
57
  j.identifier("userAttributes"),
57
58
  j.identifier("invitationToken")
58
59
  )
@@ -1,11 +1,20 @@
1
1
  import { useEffect, useState } from 'react'
2
2
 
3
+ import type {
4
+ AcceptInvitationMutation,
5
+ AcceptInvitationMutationVariables,
6
+ } from 'types/graphql'
7
+
3
8
  import { navigate, routes, useParams } from '@cedarjs/router'
4
9
  import { Metadata, useMutation } from '@cedarjs/web'
10
+ import type { TypedDocumentNode } from '@cedarjs/web'
5
11
 
6
12
  import { useAuth } from 'src/auth'
7
13
 
8
- const ACCEPT_INVITATION_MUTATION = gql`
14
+ const ACCEPT_INVITATION_MUTATION: TypedDocumentNode<
15
+ AcceptInvitationMutation,
16
+ AcceptInvitationMutationVariables
17
+ > = gql`
9
18
  mutation AcceptInvitationMutation($token: String!) {
10
19
  acceptInvitation(token: $token) {
11
20
  id
@@ -26,7 +26,6 @@ export const standard = defineScenario<
26
26
  email: 'owner@example.com',
27
27
  hashedPassword: 'x',
28
28
  salt: 'x',
29
- fullName: 'Org Owner',
30
29
  },
31
30
  },
32
31
  },
@@ -40,7 +39,6 @@ export const standard = defineScenario<
40
39
  email: 'admin@example.com',
41
40
  hashedPassword: 'x',
42
41
  salt: 'x',
43
- fullName: 'Org Admin',
44
42
  },
45
43
  },
46
44
  },
@@ -199,7 +199,6 @@ describe('organizations service', () => {
199
199
  email: 'second-owner@example.com',
200
200
  hashedPassword: 'x',
201
201
  salt: 'x',
202
- fullName: 'Second Owner',
203
202
  },
204
203
  })
205
204
  await db.membership.create({
@@ -233,7 +232,6 @@ describe('organizations service', () => {
233
232
  email: 'invitee@example.com',
234
233
  hashedPassword: 'x',
235
234
  salt: 'x',
236
- fullName: 'Invitee',
237
235
  },
238
236
  })
239
237
 
@@ -255,7 +253,6 @@ describe('organizations service', () => {
255
253
  email: 'invitee@example.com',
256
254
  hashedPassword: 'x',
257
255
  salt: 'x',
258
- fullName: 'Invitee',
259
256
  },
260
257
  })
261
258
 
@@ -278,7 +275,6 @@ describe('organizations service', () => {
278
275
  email: 'first@example.com',
279
276
  hashedPassword: 'x',
280
277
  salt: 'x',
281
- fullName: 'First User',
282
278
  },
283
279
  })
284
280
  const secondUser = await db.user.create({
@@ -286,7 +282,6 @@ describe('organizations service', () => {
286
282
  email: 'second@example.com',
287
283
  hashedPassword: 'x',
288
284
  salt: 'x',
289
- fullName: 'Second User',
290
285
  },
291
286
  })
292
287
 
@@ -308,7 +303,6 @@ describe('organizations service', () => {
308
303
  email: 'invitee@example.com',
309
304
  hashedPassword: 'x',
310
305
  salt: 'x',
311
- fullName: 'Invitee',
312
306
  },
313
307
  })
314
308
 
@@ -355,7 +349,6 @@ describe('organizations service', () => {
355
349
  email: 'invitee@example.com',
356
350
  hashedPassword: 'x',
357
351
  salt: 'x',
358
- fullName: 'Invitee',
359
352
  },
360
353
  })
361
354
 
@@ -375,7 +368,6 @@ describe('organizations service', () => {
375
368
  email: 'brand-new@example.com',
376
369
  hashedPassword: 'x',
377
370
  salt: 'x',
378
- fullName: 'Brand New',
379
371
  },
380
372
  })
381
373
 
@@ -321,7 +321,7 @@ export const removeMember: MutationResolvers['removeMember'] = async ({
321
321
 
322
322
  export const Membership: MembershipRelationResolvers = {
323
323
  organization: (_obj, { root }) =>
324
- db.membership.findUnique({ where: { id: root?.id } }).organization(),
324
+ db.membership.findUniqueOrThrow({ where: { id: root.id } }).organization(),
325
325
  }
326
326
 
327
327
  interface EnsureDefaultOrganizationArgs {
@@ -7,7 +7,7 @@ import requireMembership from './requireMembership.js'
7
7
  * selects more fields than these tests need.
8
8
  */
9
9
  const asCurrentUser = (user: { id: string; memberships?: unknown[] }) =>
10
- user as unknown as typeof context.currentUser
10
+ user as unknown as NonNullable<typeof context.currentUser>
11
11
 
12
12
  describe('requireMembership directive', () => {
13
13
  it('declares the directive sdl as schema, with the correct name', () => {
@@ -57,7 +57,7 @@ const SIGNUP_SNIPPET = `
57
57
 
58
58
  await ensureDefaultOrganization({
59
59
  currentUser: user,
60
- invitationToken: userAttributes.invitationToken,
60
+ invitationToken: userAttributes?.invitationToken,
61
61
  })
62
62
 
63
63
  return user
@@ -6,7 +6,6 @@ import { Listr } from "listr2";
6
6
  import { terminalLink } from "termi-link";
7
7
  import { colors as c } from "@cedarjs/cli-helpers/colors";
8
8
  import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display";
9
- import { runBin } from "@cedarjs/cli-helpers/packageManager/exec";
10
9
  import {
11
10
  addRootPackages,
12
11
  addWorkspacePackages
@@ -14,36 +13,24 @@ import {
14
13
  import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
15
14
  import { errorTelemetry } from "@cedarjs/telemetry";
16
15
  import { getPaths, usingVSCode } from "../../../../lib/index.js";
17
- const tailwindDirectives = [
18
- "@tailwind base;",
19
- "@tailwind components;",
20
- "@tailwind utilities;"
21
- ];
22
- const tailwindDirectivesExist = (indexCSS) => tailwindDirectives.every(
23
- (tailwindDirective) => indexCSS.includes(tailwindDirective)
24
- );
25
- const tailwindImportsAndNotes = [
16
+ import { insertPluginsBeforeCedar } from "../../deploy/helpers/index.js";
17
+ const tailwindImport = "@import 'tailwindcss';";
18
+ const tailwindImportRegExp = /@import\s+['"]tailwindcss['"]/;
19
+ const tailwindImportAndNotes = [
26
20
  "/**",
27
21
  " * START --- SETUP TAILWINDCSS EDIT",
28
22
  " *",
29
- ` * \`${formatCedarCommand(["setup", "ui", "tailwindcss"])}\` placed these directives here`,
23
+ ` * \`${formatCedarCommand(["setup", "ui", "tailwindcss"])}\` placed this import here`,
30
24
  " * to inject Tailwind's styles into your CSS.",
31
25
  " * For more information, see: https://tailwindcss.com/docs/installation",
32
26
  " */",
33
- ...tailwindDirectives,
27
+ tailwindImport,
34
28
  "/**",
35
29
  " * END --- SETUP TAILWINDCSS EDIT",
36
30
  " */\n"
37
31
  ];
38
- const recommendedVSCodeExtensions = [
39
- "csstools.postcss",
40
- "bradlc.vscode-tailwindcss"
41
- ];
32
+ const recommendedVSCodeExtensions = ["bradlc.vscode-tailwindcss"];
42
33
  const recommendationTexts = {
43
- "csstools.postcss": terminalLink(
44
- "PostCSS Language Support",
45
- "https://marketplace.visualstudio.com/items?itemName=csstools.postcss"
46
- ),
47
34
  "bradlc.vscode-tailwindcss": terminalLink(
48
35
  "Tailwind CSS IntelliSense",
49
36
  "https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss"
@@ -87,9 +74,8 @@ const handler = async ({
87
74
  const rwPaths = getPaths();
88
75
  const projectPackages = ["prettier-plugin-tailwindcss@^0.8.0"];
89
76
  const webWorkspacePackages = [
90
- "postcss",
91
- "tailwindcss@^3.4.17",
92
- "autoprefixer"
77
+ "tailwindcss@^4.3.3",
78
+ "@tailwindcss/vite@^4.3.3"
93
79
  ];
94
80
  const tasks = new Listr(
95
81
  [
@@ -140,62 +126,45 @@ const handler = async ({
140
126
  }
141
127
  },
142
128
  {
143
- title: "Configuring PostCSS...",
144
- task: () => {
145
- const postCSSConfigPath = rwPaths.web.postcss;
146
- if (!force && fs.existsSync(postCSSConfigPath)) {
129
+ title: "Adding the Tailwind CSS plugin to the Vite config...",
130
+ task: (_ctx, task) => {
131
+ const viteConfigPath = rwPaths.web.viteConfig;
132
+ if (!viteConfigPath || !fs.existsSync(viteConfigPath)) {
133
+ throw new Error("Could not find web/vite.config.{ts,js}");
134
+ }
135
+ const viteConfig = fs.readFileSync(viteConfigPath, "utf-8");
136
+ if (viteConfig.includes("@tailwindcss/vite")) {
137
+ task.skip("The Tailwind CSS Vite plugin is already configured");
138
+ return;
139
+ }
140
+ const viteConfigWithPlugin = insertPluginsBeforeCedar({
141
+ content: viteConfig,
142
+ pluginCodes: ["tailwindcss()"]
143
+ });
144
+ if (!viteConfigWithPlugin) {
147
145
  throw new Error(
148
- "PostCSS config already exists.\nUse --force to override existing config."
146
+ `Could not find \`cedar()\` in the plugins array in ${viteConfigPath}.
147
+ Add \`import tailwindcss from '@tailwindcss/vite'\` to the top of the file and \`tailwindcss()\` to the plugins array yourself, and then run this command again.`
149
148
  );
150
- } else {
151
- const postCSSConfig = fs.readFileSync(
152
- path.join(
153
- import.meta.dirname,
154
- "../templates/postcss.config.cjs.template"
155
- ),
156
- "utf-8"
157
- );
158
- fs.mkdirSync(path.dirname(postCSSConfigPath), { recursive: true });
159
- return fs.writeFileSync(postCSSConfigPath, postCSSConfig);
160
149
  }
161
- }
162
- },
163
- {
164
- title: "Initializing Tailwind CSS...",
165
- task: async () => {
166
- const tailwindConfigPath = path.join(
167
- rwPaths.web.config,
168
- "tailwind.config.cjs"
150
+ const tailwindPluginImport = "import tailwindcss from '@tailwindcss/vite'\n";
151
+ const importAnchor = /^import .* from ['"]vite['"]/m.exec(viteConfigWithPlugin) ?? /^import /m.exec(viteConfigWithPlugin);
152
+ const insertAt = importAnchor?.index ?? 0;
153
+ fs.writeFileSync(
154
+ viteConfigPath,
155
+ viteConfigWithPlugin.slice(0, insertAt) + tailwindPluginImport + viteConfigWithPlugin.slice(insertAt)
169
156
  );
170
- if (fs.existsSync(tailwindConfigPath)) {
171
- if (force) {
172
- fs.unlinkSync(tailwindConfigPath);
173
- } else {
174
- throw new Error(
175
- "Tailwind CSS config already exists.\nUse --force to override existing config."
176
- );
177
- }
178
- }
179
- await runBin("tailwindcss", ["init", tailwindConfigPath], {
180
- cwd: rwPaths.web.base
181
- });
182
- const tailwindConfig = fs.readFileSync(tailwindConfigPath, "utf-8");
183
- const newTailwindConfig = 'const { join } = require("node:path");\n\n' + tailwindConfig.replace(
184
- "content: []",
185
- "content: [join(__dirname, '../src/**/*.{js,jsx,ts,tsx}')]"
186
- ).replace("export default {", "module.exports = {");
187
- fs.writeFileSync(tailwindConfigPath, newTailwindConfig);
188
157
  }
189
158
  },
190
159
  {
191
- title: "Adding directives to index.css...",
160
+ title: "Adding Tailwind CSS import to index.css...",
192
161
  task: (_ctx, task) => {
193
162
  const INDEX_CSS_PATH = path.join(rwPaths.web.src, "index.css");
194
163
  const indexCSS = fs.readFileSync(INDEX_CSS_PATH, "utf-8");
195
- if (tailwindDirectivesExist(indexCSS)) {
196
- task.skip("Directives already exist in index.css");
164
+ if (tailwindImportRegExp.test(indexCSS)) {
165
+ task.skip("Tailwind CSS import already exists in index.css");
197
166
  } else {
198
- const newIndexCSS = tailwindImportsAndNotes.join("\n") + indexCSS;
167
+ const newIndexCSS = tailwindImportAndNotes.join("\n") + indexCSS;
199
168
  fs.writeFileSync(INDEX_CSS_PATH, newIndexCSS);
200
169
  }
201
170
  }
@@ -276,7 +245,9 @@ const handler = async ({
276
245
  "activeClassName",
277
246
  "errorClassName"
278
247
  ];
248
+ const fileAssociations = { "*.css": "tailwindcss" };
279
249
  let newSettingsJson = {
250
+ ["files.associations"]: fileAssociations,
280
251
  ["tailwindCSS.classAttributes"]: classAttributes
281
252
  };
282
253
  if (fs.existsSync(VS_CODE_SETTINGS_PATH)) {
@@ -294,8 +265,13 @@ const handler = async ({
294
265
  const mergedClassAttributes = Array.from(
295
266
  /* @__PURE__ */ new Set([...classAttributes, ...originalTwClassAttributesJson])
296
267
  );
268
+ const existingAssociations = originalSettingsJson["files.associations"];
297
269
  newSettingsJson = {
298
270
  ...originalSettingsJson,
271
+ ["files.associations"]: {
272
+ ...existingAssociations && typeof existingAssociations === "object" && !Array.isArray(existingAssociations) ? existingAssociations : {},
273
+ ...fileAssociations
274
+ },
299
275
  ["tailwindCSS.classAttributes"]: mergedClassAttributes
300
276
  };
301
277
  }
@@ -306,34 +282,38 @@ const handler = async ({
306
282
  }
307
283
  },
308
284
  {
309
- title: "Adding tailwind config entry in prettier...",
285
+ title: "Adding tailwind stylesheet entry in prettier...",
310
286
  task: async (_ctx) => {
311
287
  const prettierConfigPath = path.join(
312
288
  rwPaths.base,
313
289
  "prettier.config.cjs"
314
290
  );
315
291
  const prettierConfig = fs.readFileSync(prettierConfigPath, "utf-8");
316
- const tailwindConfigPath = path.relative(
317
- rwPaths.base,
318
- path.posix.join(rwPaths.web.config, "tailwind.config.cjs")
319
- ).replaceAll("\\", "/");
292
+ const tailwindStylesheetPath = path.relative(rwPaths.base, path.join(rwPaths.web.src, "index.css")).replaceAll("\\", "/");
293
+ const tailwindStylesheetEntry = `tailwindStylesheet: './${tailwindStylesheetPath}',`;
320
294
  let newPrettierConfig = prettierConfig;
321
- if (newPrettierConfig.includes("tailwindConfig: ")) {
322
- if (force) {
323
- newPrettierConfig = newPrettierConfig.replace(
324
- /tailwindConfig: .*(,)?/,
325
- `tailwindConfig: './${tailwindConfigPath}',`
295
+ const stringEntryRegExp = /tailwindStylesheet:\s*(['"`])[^'"`\n]*\1,?/;
296
+ if (newPrettierConfig.includes("tailwindStylesheet")) {
297
+ if (!force) {
298
+ throw new Error(
299
+ "tailwindStylesheet setting already exists in prettier configuration.\nUse --force to override existing config."
326
300
  );
327
- } else {
301
+ }
302
+ if (!stringEntryRegExp.test(newPrettierConfig)) {
328
303
  throw new Error(
329
- "tailwindConfig setting already exists in prettier configuration.\nUse --force to override existing config."
304
+ `Could not update the tailwindStylesheet setting in ${prettierConfigPath}.
305
+ Set it to './${tailwindStylesheetPath}' yourself.`
330
306
  );
331
307
  }
308
+ newPrettierConfig = newPrettierConfig.replace(
309
+ stringEntryRegExp,
310
+ tailwindStylesheetEntry
311
+ );
332
312
  } else {
333
313
  newPrettierConfig = newPrettierConfig.replace(
334
314
  /,(\n\s*)(\}\n?)$/,
335
315
  `,
336
- tailwindConfig: './${tailwindConfigPath}',$1$2`
316
+ ${tailwindStylesheetEntry}$1$2`
337
317
  );
338
318
  }
339
319
  fs.writeFileSync(prettierConfigPath, newPrettierConfig);
@@ -0,0 +1,17 @@
1
+ const UPLOADS_GITIGNORE_ENTRY = "api/.uploads";
2
+ function transformGitignore(source) {
3
+ const activeLines = source.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0 && !line.startsWith("#"));
4
+ if (activeLines.includes(UPLOADS_GITIGNORE_ENTRY)) {
5
+ return source;
6
+ }
7
+ if (/^dev\.db\*$/m.test(source)) {
8
+ return source.replace(/^(dev\.db\*)$/m, `$1
9
+ ${UPLOADS_GITIGNORE_ENTRY}`);
10
+ }
11
+ const prefix = source === "" || source.endsWith("\n") ? source : source + "\n";
12
+ return prefix + UPLOADS_GITIGNORE_ENTRY + "\n";
13
+ }
14
+ export {
15
+ UPLOADS_GITIGNORE_ENTRY,
16
+ transformGitignore
17
+ };
@@ -1,3 +1,4 @@
1
+ import { isBuiltin } from "node:module";
1
2
  const START_LINE = /^([ \t]*)await server\.start\(\)/m;
2
3
  const FACTORY_IMPORT = /import\s*\{[^}]*\bcreateAuthDecoder\b[^}]*\}\s*from\s*['"](@cedarjs\/auth-[a-z0-9-]+-api)['"]/;
3
4
  const DECODER_IMPORT = /import\s*\{[^}]*\bauthDecoder\b[^}]*\}\s*from\s*['"](@cedarjs\/auth-[a-z0-9-]+-api)['"]/;
@@ -48,21 +49,20 @@ function hasBinding(source, name) {
48
49
  return importedBindings(source).has(name) || declared.test(source);
49
50
  }
50
51
  function uploadsServerImports(source, auth) {
51
- const packageSpecs = [];
52
- const srcSpecs = [];
52
+ const specs = [];
53
53
  if (auth) {
54
54
  if (auth.usesFactory) {
55
55
  if (!hasBinding(source, "authDecoder")) {
56
- packageSpecs.push({
56
+ specs.push({
57
57
  names: ["createAuthDecoder"],
58
58
  from: auth.decoderPackage
59
59
  });
60
60
  }
61
61
  } else {
62
- packageSpecs.push({ names: ["authDecoder"], from: auth.decoderPackage });
62
+ specs.push({ names: ["authDecoder"], from: auth.decoderPackage });
63
63
  }
64
64
  }
65
- packageSpecs.push({
65
+ specs.push({
66
66
  names: auth ? ["cedarUploadsPlugin", "createUploadAuthenticator"] : ["cedarUploadsPlugin"],
67
67
  from: "@cedarjs/uploads"
68
68
  });
@@ -71,20 +71,97 @@ function uploadsServerImports(source, auth) {
71
71
  if (auth.usesFactory && !hasBinding(source, "authDecoder")) {
72
72
  authNames.unshift("cookieName");
73
73
  }
74
- srcSpecs.push({ names: authNames, from: "src/lib/auth" });
74
+ specs.push({ names: authNames, from: "src/lib/auth" });
75
75
  }
76
- srcSpecs.push({ names: ["db"], from: "src/lib/db" });
77
- srcSpecs.push({ names: ["targets"], from: "src/lib/uploads" });
78
- const render = (specs) => specs.map((spec) => ({
76
+ specs.push({ names: ["db"], from: "src/lib/db" });
77
+ specs.push({ names: ["targets"], from: "src/lib/uploads" });
78
+ return specs.map((spec) => ({
79
79
  ...spec,
80
80
  names: spec.names.filter((name) => !hasBinding(source, name))
81
- })).filter((spec) => spec.names.length > 0).map((spec) => `import { ${spec.names.join(", ")} } from '${spec.from}'`);
82
- const packageLines = render(packageSpecs);
83
- const srcLines = render(srcSpecs);
84
- if (packageLines.length > 0 && srcLines.length > 0) {
85
- return [...packageLines, "", ...srcLines];
81
+ })).filter((spec) => spec.names.length > 0).map((spec) => ({
82
+ line: `import { ${spec.names.join(", ")} } from '${spec.from}'`,
83
+ from: spec.from
84
+ }));
85
+ }
86
+ function findImports(lines) {
87
+ const imports = [];
88
+ for (let start = 0; start < lines.length; start++) {
89
+ if (!/^import\b/.test(lines[start])) {
90
+ continue;
91
+ }
92
+ const sideEffect = /^import\s*['"]([^'"]+)['"]/.exec(lines[start]);
93
+ if (sideEffect) {
94
+ imports.push({ start, end: start, from: sideEffect[1] });
95
+ continue;
96
+ }
97
+ for (let end = start; end < lines.length; end++) {
98
+ const from = /\bfrom\s*['"]([^'"]+)['"]/.exec(lines[end]);
99
+ if (from) {
100
+ imports.push({ start, end, from: from[1] });
101
+ start = end;
102
+ break;
103
+ }
104
+ }
105
+ }
106
+ return imports;
107
+ }
108
+ function importGroup(from) {
109
+ if (isBuiltin(from)) {
110
+ return 0;
111
+ }
112
+ if (from === "react") {
113
+ return 1;
114
+ }
115
+ if (from.startsWith("@cedarjs/")) {
116
+ return 3;
117
+ }
118
+ if (from.startsWith("src/")) {
119
+ return /^src\/[^/]+\/.+\.(?:sdl\.)?(?:js|ts)$/.test(from) ? 4 : 5;
86
120
  }
87
- return [...packageLines, ...srcLines];
121
+ if (from === ".." || from.startsWith("../")) {
122
+ return 6;
123
+ }
124
+ if (/^\.\/?(?:index(?:\.[jt]sx?)?)?$/.test(from)) {
125
+ return 8;
126
+ }
127
+ if (from.startsWith("./")) {
128
+ return 7;
129
+ }
130
+ return 2;
131
+ }
132
+ function compareSpecifiers(a, b) {
133
+ const segmentsA = a.toLowerCase().split("/");
134
+ const segmentsB = b.toLowerCase().split("/");
135
+ for (let i = 0; i < Math.min(segmentsA.length, segmentsB.length); i++) {
136
+ if (segmentsA[i] !== segmentsB[i]) {
137
+ return segmentsA[i] < segmentsB[i] ? -1 : 1;
138
+ }
139
+ }
140
+ return segmentsA.length - segmentsB.length;
141
+ }
142
+ function insertImport(lines, serverImport) {
143
+ const imports = findImports(lines);
144
+ const group = importGroup(serverImport.from);
145
+ const sameGroup = imports.filter((i) => importGroup(i.from) === group);
146
+ if (sameGroup.length > 0) {
147
+ const next = sameGroup.find(
148
+ (i) => compareSpecifiers(i.from, serverImport.from) > 0
149
+ );
150
+ const at2 = next ? next.start : sameGroup[sameGroup.length - 1].end + 1;
151
+ return [...lines.slice(0, at2), serverImport.line, ...lines.slice(at2)];
152
+ }
153
+ const earlier = imports.filter((i) => importGroup(i.from) < group);
154
+ if (earlier.length > 0) {
155
+ const at2 = Math.max(...earlier.map((i) => i.end)) + 1;
156
+ const inserted = ["", serverImport.line];
157
+ if (at2 < lines.length && lines[at2].trim() !== "") {
158
+ inserted.push("");
159
+ }
160
+ return [...lines.slice(0, at2), ...inserted, ...lines.slice(at2)];
161
+ }
162
+ const later = imports.find((i) => importGroup(i.from) > group);
163
+ const at = later ? later.start : 0;
164
+ return [...lines.slice(0, at), serverImport.line, "", ...lines.slice(at)];
88
165
  }
89
166
  function uploadsServerRegistration(source, auth) {
90
167
  if (!auth) {
@@ -122,18 +199,7 @@ function addUploadsPlugin(source, { auth = null } = {}) {
122
199
  if (!start) {
123
200
  throw new Error("CEDAR_UPLOADS_ERR_NO_START");
124
201
  }
125
- const lines = source.split("\n");
126
- let lastImport = -1;
127
- for (const [index, line] of lines.entries()) {
128
- if (/^import\s/.test(line)) {
129
- lastImport = index;
130
- }
131
- }
132
- const withImports = [
133
- ...lines.slice(0, lastImport + 1),
134
- ...uploadsServerImports(source, auth),
135
- ...lines.slice(lastImport + 1)
136
- ].join("\n");
202
+ const withImports = uploadsServerImports(source, auth).reduce(insertImport, source.split("\n")).join("\n");
137
203
  return withImports.replace(
138
204
  START_LINE,
139
205
  (line) => `${uploadsServerRegistration(source, auth)}