@cedarjs/cli 2.2.1 → 2.2.2-next.31

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.
@@ -9,7 +9,7 @@ const paths = getPaths();
9
9
  const loadPrismaClient = (replContext) => {
10
10
  const createdRequire = createRequire(import.meta.url);
11
11
  const { db } = createdRequire(path.join(paths.api.lib, "db"));
12
- db[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")] = "PrismaClient";
12
+ db[Symbol.for("nodejs.util.inspect.custom")] = "PrismaClient";
13
13
  replContext.db = db;
14
14
  };
15
15
  const consoleHistoryFile = path.join(paths.generated.base, "console_history");
@@ -0,0 +1,73 @@
1
+ import path from "node:path";
2
+ import { transformTSToJS } from "../../../lib/index.js";
3
+ import { templateForFile } from "../yargsHandlerHelpers.js";
4
+ const files = async ({
5
+ name,
6
+ folderName,
7
+ packageName,
8
+ fileName,
9
+ typescript,
10
+ tests: generateTests = true,
11
+ ...rest
12
+ }) => {
13
+ const extension = typescript ? ".ts" : ".js";
14
+ const outputFiles = [];
15
+ const indexFile = await templateForFile({
16
+ name,
17
+ side: "packages",
18
+ generator: "package",
19
+ templatePath: "index.ts.template",
20
+ templateVars: rest,
21
+ outputPath: path.join(folderName, "src", `index${extension}`)
22
+ });
23
+ const readmeFile = await templateForFile({
24
+ name,
25
+ side: "packages",
26
+ generator: "package",
27
+ templatePath: "README.md.template",
28
+ templateVars: { packageName, ...rest },
29
+ outputPath: path.join(folderName, "README.md")
30
+ });
31
+ const packageJsonFile = await templateForFile({
32
+ name,
33
+ side: "packages",
34
+ generator: "package",
35
+ templatePath: "package.json.template",
36
+ templateVars: { packageName, ...rest },
37
+ outputPath: path.join(folderName, "package.json")
38
+ });
39
+ const tsconfigFile = await templateForFile({
40
+ name,
41
+ side: "packages",
42
+ generator: "package",
43
+ templatePath: "tsconfig.json.template",
44
+ templateVars: { packageName, ...rest },
45
+ outputPath: path.join(folderName, "tsconfig.json")
46
+ });
47
+ outputFiles.push(indexFile);
48
+ outputFiles.push(readmeFile);
49
+ outputFiles.push(packageJsonFile);
50
+ outputFiles.push(tsconfigFile);
51
+ if (generateTests) {
52
+ const testFile = await templateForFile({
53
+ name,
54
+ side: "packages",
55
+ generator: "package",
56
+ templatePath: "test.ts.template",
57
+ templateVars: rest,
58
+ outputPath: path.join(folderName, "src", `${fileName}.test${extension}`)
59
+ });
60
+ outputFiles.push(testFile);
61
+ }
62
+ return outputFiles.reduce(async (accP, [outputPath, content]) => {
63
+ const acc = await accP;
64
+ const template = typescript || outputPath.endsWith(".md") || outputPath.endsWith(".json") ? content : await transformTSToJS(outputPath, content);
65
+ return {
66
+ [outputPath]: template,
67
+ ...acc
68
+ };
69
+ }, Promise.resolve({}));
70
+ };
71
+ export {
72
+ files
73
+ };
@@ -0,0 +1,14 @@
1
+ import { createHandler, createBuilder } from "../yargsCommandHelpers.js";
2
+ const command = "package <name>";
3
+ const description = "Generate a workspace Package";
4
+ const builder = createBuilder({
5
+ componentName: "package",
6
+ addStories: false
7
+ });
8
+ const handler = createHandler("package");
9
+ export {
10
+ builder,
11
+ command,
12
+ description,
13
+ handler
14
+ };
@@ -0,0 +1,158 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { paramCase, camelCase } from "change-case";
4
+ import execa from "execa";
5
+ import { Listr } from "listr2";
6
+ import { terminalLink } from "termi-link";
7
+ import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
8
+ import { getConfig } from "@cedarjs/project-config";
9
+ import { errorTelemetry } from "@cedarjs/telemetry";
10
+ import c from "../../../lib/colors.js";
11
+ import { getPaths, writeFilesTask } from "../../../lib/index.js";
12
+ import { prepareForRollback } from "../../../lib/rollback.js";
13
+ import { files } from "./filesTask.js";
14
+ function nameVariants(nameArg) {
15
+ const base = path.basename(getPaths().base);
16
+ const [orgName, name] = nameArg.startsWith("@") ? nameArg.slice(1).split("/", 2) : [paramCase(base), nameArg];
17
+ const folderName = paramCase(name);
18
+ const packageName = "@" + paramCase(orgName) + "/" + folderName;
19
+ const fileName = camelCase(name);
20
+ return { name, folderName, packageName, fileName };
21
+ }
22
+ async function updateTsconfig(task) {
23
+ const tsconfigPath = path.join(getPaths().api.base, "tsconfig.json");
24
+ const tsconfig = await fs.promises.readFile(tsconfigPath, "utf8");
25
+ const tsconfigLines = tsconfig.split("\n");
26
+ const moduleLineIndex = tsconfigLines.findIndex(
27
+ (line) => /^\s*"module":\s*"/.test(line)
28
+ );
29
+ const moduleLine = tsconfigLines[moduleLineIndex];
30
+ if (moduleLine.toLowerCase().includes("node20") || // While Cedar doesn't officially endorse the usage of NodeNext, it
31
+ // will still work here, so I won't overwrite it
32
+ moduleLine.toLowerCase().includes("nodenext")) {
33
+ task.skip("tsconfig already up to date");
34
+ return;
35
+ }
36
+ tsconfigLines[moduleLineIndex] = moduleLine.replace(
37
+ /":\s*"[\w\d]+"/,
38
+ '": "Node20"'
39
+ );
40
+ await fs.promises.writeFile(tsconfigPath, tsconfigLines.join("\n"));
41
+ }
42
+ async function installAndBuild(folderName) {
43
+ const packagePath = path.join("packages", folderName);
44
+ await execa("yarn", ["install"], { stdio: "inherit", cwd: getPaths().base });
45
+ await execa("yarn", ["build"], { stdio: "inherit", cwd: packagePath });
46
+ }
47
+ const handler = async ({ name, force, ...rest }) => {
48
+ recordTelemetryAttributes({
49
+ command: "generate package",
50
+ force,
51
+ rollback: rest.rollback
52
+ });
53
+ if (name.replaceAll("/", "").length < name.length - 1) {
54
+ throw new Error(
55
+ `Invalid package name "${name}". Package names can have at most one slash.`
56
+ );
57
+ }
58
+ if (!getConfig().experimental.packagesWorkspace.enabled) {
59
+ const releaseNotes = terminalLink(
60
+ "release notes",
61
+ "https://github.com/cedarjs/cedar/releases"
62
+ );
63
+ console.error(
64
+ "This is an experimental feature. Please enable it in your redwood.toml file and then run this command again."
65
+ );
66
+ console.error();
67
+ console.error(`See the ${releaseNotes} for instructions on how to enable.`);
68
+ return;
69
+ }
70
+ let packageFiles = {};
71
+ const tasks = new Listr(
72
+ /** @type {import('listr2').ListrTask<ListrContext>[]} */
73
+ [
74
+ {
75
+ title: "Parsing package name...",
76
+ task: (ctx) => {
77
+ ctx.nameVariants = nameVariants(name);
78
+ }
79
+ },
80
+ {
81
+ title: "Updating workspace config...",
82
+ task: async (ctx, task) => {
83
+ const rootPackageJsonPath = path.join(getPaths().base, "package.json");
84
+ const packageJson = JSON.parse(
85
+ await fs.promises.readFile(rootPackageJsonPath, "utf8")
86
+ );
87
+ if (!Array.isArray(packageJson.workspaces)) {
88
+ throw new Error(
89
+ "Invalid workspace config in " + rootPackageJsonPath
90
+ );
91
+ }
92
+ const packagePath = `packages/${ctx.nameVariants.folderName}`;
93
+ const hasWildcardPackagesWorkspace = packageJson.workspaces.includes("packages/*");
94
+ const hasNamedPackagesWorkspace = packageJson.workspaces.includes(packagePath);
95
+ const hasOtherNamedPackages = packageJson.workspaces.some(
96
+ (workspace) => workspace.startsWith("packages/") && workspace !== packagePath
97
+ );
98
+ if (hasWildcardPackagesWorkspace || hasNamedPackagesWorkspace) {
99
+ task.skip("Workspaces already configured");
100
+ } else {
101
+ if (hasOtherNamedPackages) {
102
+ packageJson.workspaces.push(packagePath);
103
+ } else {
104
+ packageJson.workspaces.push("packages/*");
105
+ }
106
+ await fs.promises.writeFile(
107
+ rootPackageJsonPath,
108
+ JSON.stringify(packageJson, null, 2)
109
+ );
110
+ }
111
+ }
112
+ },
113
+ {
114
+ title: "Updating api side tsconfig file...",
115
+ task: (_ctx, task) => updateTsconfig(task)
116
+ },
117
+ {
118
+ title: "Generating package files...",
119
+ task: async (ctx) => {
120
+ packageFiles = await files({ ...ctx.nameVariants, ...rest });
121
+ return writeFilesTask(packageFiles, { overwriteExisting: force });
122
+ }
123
+ },
124
+ {
125
+ title: "Installing and building...",
126
+ task: (ctx) => installAndBuild(ctx.nameVariants.folderName)
127
+ },
128
+ {
129
+ title: "Cleaning up...",
130
+ task: () => {
131
+ execa.sync("yarn", [
132
+ "eslint",
133
+ "--fix",
134
+ "--config",
135
+ `${getPaths().base}/node_modules/@cedarjs/eslint-config/index.js`,
136
+ ...Object.keys(packageFiles)
137
+ ]);
138
+ }
139
+ }
140
+ ],
141
+ { rendererOptions: { collapseSubtasks: false }, exitOnError: true }
142
+ );
143
+ try {
144
+ if (rest.rollback && !force) {
145
+ prepareForRollback(tasks);
146
+ }
147
+ await tasks.run();
148
+ } catch (e) {
149
+ errorTelemetry(process.argv, e.message);
150
+ console.error(c.error(e.message));
151
+ process.exit(e?.exitCode || 1);
152
+ }
153
+ };
154
+ export {
155
+ handler,
156
+ nameVariants,
157
+ updateTsconfig
158
+ };
@@ -0,0 +1,15 @@
1
+ # Shared Package '${packageName}'
2
+
3
+ Use code in this package by adding it to the dependencies on the side you want
4
+ to use it, with the special `workspace:*` version. After that you can import it
5
+ into your code:
6
+
7
+ ```json
8
+ "dependencies": {
9
+ "${packageName}": "workspace:*"
10
+ }
11
+ ```
12
+
13
+ ```javascript
14
+ import { ${camelName} } from '${packageName}';
15
+ ```
@@ -0,0 +1,3 @@
1
+ export function ${camelName}() {
2
+ return 0
3
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "${packageName}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "watch": "tsc --watch"
10
+ },
11
+ "devDependencies": {
12
+ "@cedarjs/testing": "2.2.1",
13
+ "typescript": "5.9.3"
14
+ }
15
+ }
@@ -0,0 +1,8 @@
1
+ import type { ScenarioData } from '@cedarjs/testing/api'
2
+
3
+ export const standard = defineScenario({
4
+ // Define the "fixture" to write into your test database here
5
+ // See guide: https://cedarjs.com/docs/testing#scenarios
6
+ })
7
+
8
+ export type StandardScenario = ScenarioData<unknown>
@@ -0,0 +1,7 @@
1
+ import { ${camelName} } from './index.js'
2
+
3
+ describe('${camelName}', () => {
4
+ it('should not throw any errors', async () => {
5
+ expect(${camelName}()).not.toThrow()
6
+ })
7
+ })
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "target": "ES2023",
5
+ "module": "Node20",
6
+ "esModuleInterop": true,
7
+ "skipLibCheck": true,
8
+ "baseUrl": ".",
9
+ "rootDir": "src",
10
+ "outDir": "dist",
11
+ "sourceMap": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ },
15
+ "include": ["src"],
16
+ }
@@ -1,5 +1,5 @@
1
- import path from "path";
2
- import fs from "fs-extra";
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
3
  import { Listr } from "listr2";
4
4
  import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
5
5
  import { errorTelemetry } from "@cedarjs/telemetry";
@@ -11,26 +11,27 @@ import {
11
11
  } from "../../../lib/index.js";
12
12
  import { prepareForRollback } from "../../../lib/rollback.js";
13
13
  import { validateName } from "../helpers.js";
14
- const TEMPLATE_PATH = path.resolve(
15
- import.meta.dirname,
16
- "templates",
17
- "script.ts.template"
18
- );
19
- const TSCONFIG_TEMPLATE = path.resolve(
20
- import.meta.dirname,
21
- "templates",
22
- "tsconfig.json.template"
23
- );
14
+ import { customOrDefaultTemplatePath } from "../yargsHandlerHelpers.js";
24
15
  const files = async ({ name, typescript = false }) => {
25
16
  const outputFilename = `${name}.${typescript ? "ts" : "js"}`;
26
17
  const outputPath = path.join(getPaths().scripts, outputFilename);
27
18
  const scriptTsConfigPath = path.join(getPaths().scripts, "tsconfig.json");
28
- const template = fs.readFileSync(TEMPLATE_PATH, "utf-8");
19
+ const templatePath = customOrDefaultTemplatePath({
20
+ side: "scripts",
21
+ generator: "script",
22
+ templatePath: "script.ts.template"
23
+ });
24
+ const template = fs.readFileSync(templatePath, "utf-8");
25
+ const tsconfigTemplatePath = customOrDefaultTemplatePath({
26
+ side: "scripts",
27
+ generator: "script",
28
+ templatePath: "tsconfig.json.template"
29
+ });
29
30
  return {
30
31
  [outputPath]: typescript ? template : await transformTSToJS(outputPath, template),
31
32
  // Add tsconfig for type and cmd+click support if project is TS
32
33
  ...typescript && !fs.existsSync(scriptTsConfigPath) && {
33
- [scriptTsConfigPath]: fs.readFileSync(TSCONFIG_TEMPLATE, "utf-8")
34
+ [scriptTsConfigPath]: fs.readFileSync(tsconfigTemplatePath, "utf-8")
34
35
  }
35
36
  };
36
37
  };
@@ -26,12 +26,19 @@ const customOrDefaultTemplatePath = ({
26
26
  templatePath
27
27
  );
28
28
  const customPath = path.join(
29
- getPaths()[side].generators,
29
+ getPaths().generatorTemplates,
30
+ side,
30
31
  generator,
31
32
  templatePath
32
33
  );
34
+ const deprecatedCustomPath = getPaths()[side].generators ? path.join(getPaths()[side].generators, generator, templatePath) : void 0;
33
35
  if (fs.existsSync(customPath)) {
34
36
  return customPath;
37
+ } else if (deprecatedCustomPath && fs.existsSync(deprecatedCustomPath)) {
38
+ console.log(
39
+ `Having generator templates in ${getPaths()[side].generators} has been deprecated. Please move them to ${getPaths().generatorTemplates}.`
40
+ );
41
+ return deprecatedCustomPath;
35
42
  } else {
36
43
  return defaultPath;
37
44
  }
@@ -45,20 +52,21 @@ const templateForFile = async ({
45
52
  templatePath,
46
53
  templateVars
47
54
  }) => {
48
- const basePath = getPaths()[side][sidePathSection];
55
+ const basePath = sidePathSection ? getPaths()[side][sidePathSection] : getPaths()[side];
49
56
  const fullOutputPath = path.join(basePath, outputPath);
50
57
  const fullTemplatePath = customOrDefaultTemplatePath({
51
58
  generator,
52
59
  templatePath,
53
60
  side
54
61
  });
55
- const content = await generateTemplate(fullTemplatePath, {
62
+ const mergedTemplateVars = {
56
63
  name,
57
64
  outputPath: ensurePosixPath(
58
65
  `./${path.relative(getPaths().base, fullOutputPath)}`
59
66
  ),
60
67
  ...templateVars
61
- });
68
+ };
69
+ const content = await generateTemplate(fullTemplatePath, mergedTemplateVars);
62
70
  return [fullOutputPath, content];
63
71
  };
64
72
  const templateForComponentFile = async ({
@@ -11,6 +11,7 @@ import * as generateJob from "./generate/job/job.js";
11
11
  import * as generateLayout from "./generate/layout/layout.js";
12
12
  import * as generateModel from "./generate/model/model.js";
13
13
  import * as generateOgImage from "./generate/ogImage/ogImage.js";
14
+ import * as generatePackage from "./generate/package/package.js";
14
15
  import * as generatePage from "./generate/page/page.js";
15
16
  import * as generateRealtime from "./generate/realtime/realtime.js";
16
17
  import * as generateScaffold from "./generate/scaffold/scaffold.js";
@@ -28,7 +29,7 @@ const builder = (yargs) => yargs.command("types", "Generate supplementary code",
28
29
  } catch (error) {
29
30
  process.exitCode = error.exitCode ?? 1;
30
31
  }
31
- }).command(generateCell).command(generateComponent).command(generateDataMigration).command(generateDbAuth).command(generateDirective).command(generateFunction).command(generateJob).command(generateLayout).command(generateModel).command(generateOgImage).command(generatePage).command(generateRealtime).command(generateScaffold).command(generateScript).command(generateSdl).command(generateSecret).command(generateService).demandCommand().epilogue(
32
+ }).command(generateCell).command(generateComponent).command(generateDataMigration).command(generateDbAuth).command(generateDirective).command(generateFunction).command(generateJob).command(generateLayout).command(generateModel).command(generateOgImage).command(generatePackage).command(generatePage).command(generateRealtime).command(generateScaffold).command(generateScript).command(generateSdl).command(generateSecret).command(generateService).demandCommand().epilogue(
32
33
  `Also see the ${terminalLink(
33
34
  "CedarJS CLI Reference",
34
35
  "https://cedarjs.com/docs/cli-commands#generate-alias-g"
@@ -4,13 +4,7 @@ import { terminalLink } from "termi-link";
4
4
  import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
5
5
  const command = "generator <name>";
6
6
  const description = "Copies generator templates locally for customization";
7
- const EXCLUDE_GENERATORS = [
8
- "dataMigration",
9
- "dbAuth",
10
- "generator",
11
- "script",
12
- "secret"
13
- ];
7
+ const EXCLUDE_GENERATORS = ["dataMigration", "dbAuth", "generator", "secret"];
14
8
  const builder = (yargs) => {
15
9
  const availableGenerators = fs.readdirSync(path.join(import.meta.dirname, "../../generate"), {
16
10
  withFileTypes: true
@@ -5,17 +5,22 @@ import c from "../../../lib/colors.js";
5
5
  import { getPaths } from "../../../lib/index.js";
6
6
  const SIDE_MAP = {
7
7
  web: ["cell", "component", "layout", "page", "scaffold"],
8
- api: ["function", "sdl", "service"]
8
+ api: ["function", "sdl", "service"],
9
+ scripts: ["script"],
10
+ packages: ["package"]
9
11
  };
10
12
  const copyGenerator = (name, { force }) => {
11
- const side = SIDE_MAP["web"].includes(name) ? "web" : "api";
13
+ const side = Object.keys(SIDE_MAP).find((key) => SIDE_MAP[key].includes(name));
14
+ if (!side) {
15
+ throw new Error(`Invalid generator name: ${name}`);
16
+ }
12
17
  const from = path.join(
13
18
  import.meta.dirname,
14
19
  "../../generate",
15
20
  name,
16
21
  "templates"
17
22
  );
18
- const to = path.join(getPaths()[side].generators, name);
23
+ const to = path.join(getPaths().generatorTemplates, side, name);
19
24
  fs.cpSync(from, to, { recursive: true, force });
20
25
  return to;
21
26
  };