@cedarjs/cli 7.0.0-canary.3259 → 7.0.0-rc.123
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.
- package/dist/commands/build/buildHandler.js +1 -2
- package/dist/commands/build.js +1 -1
- package/dist/commands/check.js +1 -2
- package/dist/commands/deploy/baremetal/baremetalHandler.js +1 -1
- package/dist/commands/deploy/baremetal.js +1 -1
- package/dist/commands/deploy/flightcontrolHandler.js +1 -1
- package/dist/commands/deploy/helpers/deployHandler.js +1 -1
- package/dist/commands/deploy/netlify.js +1 -1
- package/dist/commands/deploy/renderHandler.js +1 -1
- package/dist/commands/deploy/serverlessHandler.js +1 -2
- package/dist/commands/deploy/vercel.js +1 -1
- package/dist/commands/destroy/handlerHelpers.js +1 -1
- package/dist/commands/destroy/page/pageHandler.js +1 -2
- package/dist/commands/destroy/scaffold/scaffoldHandler.js +1 -2
- package/dist/commands/destroy/sdl/sdlHandler.js +1 -2
- package/dist/commands/dev/devHandler.js +4 -3
- package/dist/commands/dev/packageWatchCommands.js +3 -8
- package/dist/commands/dev.js +1 -1
- package/dist/commands/execHandler.js +1 -2
- package/dist/commands/experimental/live-queries/liveQueries.js +1 -1
- package/dist/commands/experimental/live-queries/liveQueriesHandler.js +3 -3
- package/dist/commands/experimental/setupInngest.js +1 -1
- package/dist/commands/experimental/setupInngestHandler.js +1 -1
- package/dist/commands/experimental/setupOpentelemetry.js +1 -1
- package/dist/commands/experimental/setupOpentelemetryHandler.js +1 -2
- package/dist/commands/experimental/setupReactCompiler.js +1 -1
- package/dist/commands/experimental/setupReactCompilerHandler.js +4 -4
- package/dist/commands/experimental/setupRsc.js +1 -1
- package/dist/commands/experimental/setupRscHandler.js +1 -2
- package/dist/commands/experimental/setupStreamingSsr.js +1 -1
- package/dist/commands/experimental/setupStreamingSsrHandler.js +2 -3
- package/dist/commands/generate/cell/cell.js +0 -20
- package/dist/commands/generate/cell/cellHandler.js +20 -68
- package/dist/commands/generate/cell/templates/cell.tsx.template +2 -21
- package/dist/commands/generate/cell/templates/cellList.tsx.template +2 -21
- package/dist/commands/generate/cell/templates/test.js.template +1 -1
- package/dist/commands/generate/dataMigration/dataMigration.js +1 -2
- package/dist/commands/generate/dbAuth/dbAuthHandler.js +3 -23
- package/dist/commands/generate/directive/directiveHandler.js +1 -2
- package/dist/commands/generate/function/functionHandler.js +1 -2
- package/dist/commands/generate/job/jobHandler.js +1 -2
- package/dist/commands/generate/model/modelHandler.js +1 -2
- package/dist/commands/generate/ogImage/ogImageHandler.js +1 -2
- package/dist/commands/generate/package/filesTask.js +12 -9
- package/dist/commands/generate/package/packageHandler.js +4 -3
- package/dist/commands/generate/page/pageHandler.js +1 -2
- package/dist/commands/generate/realtime/realtime.js +1 -1
- package/dist/commands/generate/realtime/realtimeHandler.js +9 -2
- package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.ts.template +1 -1
- package/dist/commands/generate/scaffold/scaffoldHandler.js +5 -10
- package/dist/commands/generate/scaffold/templates/components/NameForm.tsx.template +3 -3
- package/dist/commands/generate/scaffold/templates/lib/formatters.test.tsx.template +0 -11
- package/dist/commands/generate/scaffold/templates/lib/formatters.tsx.template +1 -13
- package/dist/commands/generate/script/scriptHandler.js +1 -2
- package/dist/commands/generate/sdl/sdlHandler.js +1 -2
- package/dist/commands/generate/secret/secret.js +1 -1
- package/dist/commands/generate/service/serviceHandler.js +38 -47
- package/dist/commands/generate/service/templates/service.ts.template +8 -22
- package/dist/commands/generate/service/templates/test.ts.template +1 -1
- package/dist/commands/generate/yargsCommandHelpers.js +1 -1
- package/dist/commands/generate/yargsHandlerHelpers.js +1 -2
- package/dist/commands/generate.js +1 -1
- package/dist/commands/info.js +1 -1
- package/dist/commands/lint.js +1 -1
- package/dist/commands/prerenderHandler.js +4 -11
- package/dist/commands/prismaHandler.js +2 -7
- package/dist/commands/record/init.js +1 -1
- package/dist/commands/serve.js +1 -2
- package/dist/commands/setup/auth/auth.js +4 -2
- package/dist/commands/setup/cache/cache.js +1 -1
- package/dist/commands/setup/cache/cacheHandler.js +1 -2
- package/dist/commands/setup/database/postgres.js +1 -1
- package/dist/commands/setup/database/postgresHandler.js +1 -3
- package/dist/commands/setup/deploy/providers/baremetalHandler.js +1 -2
- package/dist/commands/setup/deploy/providers/coherence.js +1 -1
- package/dist/commands/setup/deploy/providers/coherenceHandler.js +6 -7
- package/dist/commands/setup/deploy/providers/flightcontrolHandler.js +1 -2
- package/dist/commands/setup/deploy/providers/netlifyHandler.js +1 -2
- package/dist/commands/setup/deploy/providers/renderHandler.js +1 -2
- package/dist/commands/setup/deploy/providers/serverlessHandler.js +1 -2
- package/dist/commands/setup/deploy/providers/universalDeployHandler.js +6 -4
- package/dist/commands/setup/deploy/providers/vercelHandler.js +1 -2
- package/dist/commands/setup/docker/docker.js +1 -1
- package/dist/commands/setup/docker/dockerHandler.js +1 -2
- package/dist/commands/setup/generator/generator.js +1 -1
- package/dist/commands/setup/generator/generatorHandler.js +1 -1
- package/dist/commands/setup/graphql/features/fragments/fragments.js +1 -1
- package/dist/commands/setup/graphql/features/fragments/fragmentsHandler.js +5 -3
- package/dist/commands/setup/graphql/features/trustedDocuments/trustedDocuments.js +1 -1
- package/dist/commands/setup/graphql/features/trustedDocuments/trustedDocumentsHandler.js +1 -2
- package/dist/commands/setup/i18n/i18n.js +1 -1
- package/dist/commands/setup/i18n/i18nHandler.js +1 -1
- package/dist/commands/setup/jobs/jobs.js +1 -1
- package/dist/commands/setup/jobs/jobsHandler.js +1 -2
- package/dist/commands/setup/mailer/mailer.js +1 -1
- package/dist/commands/setup/mailer/mailerHandler.js +1 -2
- package/dist/commands/setup/middleware/ogImage/ogImage.js +1 -1
- package/dist/commands/setup/middleware/ogImage/ogImageHandler.js +1 -2
- package/dist/commands/setup/monitoring/sentry/sentry.js +1 -1
- package/dist/commands/setup/monitoring/sentry/sentryHandler.js +7 -8
- package/dist/commands/setup/neon/neon.js +1 -1
- package/dist/commands/setup/neon/neonHandler.js +9 -5
- package/dist/commands/setup/package/package.js +1 -1
- package/dist/commands/setup/package/packageHandler.js +4 -4
- package/dist/commands/setup/realtime/addRealtimeToGraphql.js +1 -1
- package/dist/commands/setup/realtime/realtime.js +1 -1
- package/dist/commands/setup/realtime/realtimeHandler.js +16 -4
- package/dist/commands/setup/realtime/templates/subscriptions/countdown/countdown.ts.template +1 -1
- package/dist/commands/setup/realtime/templates/subscriptions/newMessage/newMessage.ts.template +1 -1
- package/dist/commands/setup/server-file/serverFile.js +1 -1
- package/dist/commands/setup/server-file/serverFileHandler.js +1 -2
- package/dist/commands/setup/ui/libraries/chakra-uiHandler.js +1 -2
- package/dist/commands/setup/ui/libraries/mantineHandler.js +1 -2
- package/dist/commands/setup/ui/libraries/tailwindcssHandler.js +1 -2
- package/dist/commands/setup/uploads/dbCodemod.js +28 -0
- package/dist/commands/setup/uploads/templates/signedUrl.ts.template +21 -0
- package/dist/commands/setup/uploads/templates/srcLibUploads.ts.template +24 -23
- package/dist/commands/setup/uploads/uploads.js +7 -49
- package/dist/commands/setup/uploads/uploadsHandler.js +69 -256
- package/dist/commands/setup.js +1 -2
- package/dist/commands/studio.js +1 -1
- package/dist/commands/test/testHandler.js +71 -25
- package/dist/commands/test/testHandlerEsm.js +100 -0
- package/dist/commands/test.js +18 -10
- package/dist/commands/testEsm.js +41 -0
- package/dist/commands/type-checkHandler.js +1 -1
- package/dist/commands/upgrade/preUpgradeScripts.js +3 -3
- package/dist/commands/upgrade/upgrade.js +1 -1
- package/dist/commands/upgrade/upgradeHandler.js +1 -2
- package/dist/index.js +8 -5
- package/dist/lib/exit.js +1 -1
- package/dist/lib/index.js +25 -6
- package/dist/lib/loadEnvFiles.js +1 -1
- package/dist/lib/plugin.js +1 -1
- package/dist/lib/test.js +1 -1
- package/dist/lib/updateCheck.js +4 -4
- package/dist/middleware/checkNodeVersion.js +4 -4
- package/package.json +18 -18
- package/dist/commands/generate/cell/templates/cellFragment.tsx.template +0 -43
- package/dist/commands/generate/cell/templates/storiesFragment.tsx.template +0 -23
- package/dist/commands/generate/cell/templates/testFragment.js.template +0 -30
- package/dist/commands/generate/dbAuth/templates/login.oauth.tsx.template +0 -164
- package/dist/commands/generate/dbAuth/templates/signup.oauth.tsx.template +0 -147
- package/dist/commands/setup/tenancy/authCodemod.js +0 -66
- package/dist/commands/setup/tenancy/dbCodemod.js +0 -64
- package/dist/commands/setup/tenancy/graphqlCodemod.js +0 -88
- package/dist/commands/setup/tenancy/preflight.js +0 -49
- package/dist/commands/setup/tenancy/schemaPrisma.js +0 -81
- package/dist/commands/setup/tenancy/signupCodemod.js +0 -153
- package/dist/commands/setup/tenancy/templates/InvitePage.tsx.template +0 -63
- package/dist/commands/setup/tenancy/templates/OrgScope.tsx.template +0 -40
- package/dist/commands/setup/tenancy/templates/ensureDefaultOrganizations.dataMigration.ts.template +0 -39
- package/dist/commands/setup/tenancy/templates/organizations.scenarios.ts.template +0 -59
- package/dist/commands/setup/tenancy/templates/organizations.sdl.ts.template +0 -89
- package/dist/commands/setup/tenancy/templates/organizations.test.ts.template +0 -389
- package/dist/commands/setup/tenancy/templates/organizations.ts.template +0 -394
- package/dist/commands/setup/tenancy/templates/requireMembership.test.ts.template +0 -35
- package/dist/commands/setup/tenancy/templates/requireMembership.ts.template +0 -25
- package/dist/commands/setup/tenancy/tenancy.js +0 -30
- package/dist/commands/setup/tenancy/tenancyHandler.js +0 -425
- package/dist/commands/setup/uploads/schemaPrisma.js +0 -41
- package/dist/commands/setup/uploads/serverFile.js +0 -151
- package/dist/commands/setup/uploads/templates/requireUploadToken.ts.template +0 -15
- package/dist/commands/setup/uploads/templates/uploads.sdl.ts.template +0 -79
- package/dist/commands/setup/uploads/templates/uploads.ts.template +0 -89
- package/dist/commands/setup/uploads/templates/withDataUri.ts.template +0 -14
- package/dist/commands/setup/uploads/templates/withSignedUrl.ts.template +0 -16
- package/dist/commands/test/viteVersionCheck.js +0 -75
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { Listr } from "listr2";
|
|
4
|
-
import { colors as c } from "@cedarjs/cli-helpers
|
|
5
|
-
import { addApiPackages } from "@cedarjs/cli-helpers/installHelpers";
|
|
4
|
+
import { addApiPackages, colors as c } from "@cedarjs/cli-helpers";
|
|
6
5
|
import { errorTelemetry } from "@cedarjs/telemetry";
|
|
7
6
|
import { getPaths, transformTSToJS, writeFile } from "../../../lib/index.js";
|
|
8
7
|
import { isTypeScriptProject } from "../../../lib/project.js";
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { Listr } from "listr2";
|
|
3
|
-
import { colors as c } from "@cedarjs/cli-helpers
|
|
3
|
+
import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
|
|
4
4
|
import { addWorkspacePackages } from "@cedarjs/cli-helpers/packageManager/packages";
|
|
5
|
-
import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
|
|
6
5
|
import extendStorybookConfiguration from "../../../../lib/configureStorybook.js";
|
|
7
6
|
import { extendJSXFile, fileIncludes } from "../../../../lib/extendFile.js";
|
|
8
7
|
import { getPaths, writeFile } from "../../../../lib/index.js";
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { Listr } from "listr2";
|
|
4
|
-
import { colors as c } from "@cedarjs/cli-helpers
|
|
4
|
+
import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
|
|
5
5
|
import { addWorkspacePackages } from "@cedarjs/cli-helpers/packageManager/packages";
|
|
6
|
-
import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
|
|
7
6
|
import extendStorybookConfiguration from "../../../../lib/configureStorybook.js";
|
|
8
7
|
import { extendJSXFile, fileIncludes } from "../../../../lib/extendFile.js";
|
|
9
8
|
import { getPaths, writeFile } from "../../../../lib/index.js";
|
|
@@ -4,14 +4,13 @@ import { ListrEnquirerPromptAdapter } from "@listr2/prompt-adapter-enquirer";
|
|
|
4
4
|
import execa from "execa";
|
|
5
5
|
import { Listr } from "listr2";
|
|
6
6
|
import { terminalLink } from "termi-link";
|
|
7
|
-
import { colors as c } from "@cedarjs/cli-helpers
|
|
7
|
+
import { recordTelemetryAttributes, colors as c } from "@cedarjs/cli-helpers";
|
|
8
8
|
import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display";
|
|
9
9
|
import { runBin } from "@cedarjs/cli-helpers/packageManager/exec";
|
|
10
10
|
import {
|
|
11
11
|
addRootPackages,
|
|
12
12
|
addWorkspacePackages
|
|
13
13
|
} from "@cedarjs/cli-helpers/packageManager/packages";
|
|
14
|
-
import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
|
|
15
14
|
import { errorTelemetry } from "@cedarjs/telemetry";
|
|
16
15
|
import { getPaths, usingVSCode } from "../../../../lib/index.js";
|
|
17
16
|
const tailwindDirectives = [
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import j from "jscodeshift";
|
|
2
|
+
function transform(fileInfo) {
|
|
3
|
+
const root = j(fileInfo.source);
|
|
4
|
+
const imports = root.find(j.ImportDeclaration);
|
|
5
|
+
imports.at(-1).insertAfter(
|
|
6
|
+
j.importDeclaration(
|
|
7
|
+
[j.importSpecifier(j.identifier("storagePrismaExtension"))],
|
|
8
|
+
j.literal("./uploads.js")
|
|
9
|
+
)
|
|
10
|
+
);
|
|
11
|
+
root.find(j.VariableDeclaration, { declarations: [{ id: { name: "db" } }] }).forEach((path) => {
|
|
12
|
+
const dbDeclaration = path.node.declarations[0];
|
|
13
|
+
if (j.VariableDeclarator.check(dbDeclaration) && j.NewExpression.check(dbDeclaration.init)) {
|
|
14
|
+
throw new Error("RW_CODEMOD_ERR_OLD_FORMAT");
|
|
15
|
+
}
|
|
16
|
+
if (j.VariableDeclarator.check(dbDeclaration) && j.Expression.check(dbDeclaration.init)) {
|
|
17
|
+
const newInit = j.callExpression(
|
|
18
|
+
j.memberExpression(dbDeclaration.init, j.identifier("$extends")),
|
|
19
|
+
[j.identifier("storagePrismaExtension")]
|
|
20
|
+
);
|
|
21
|
+
dbDeclaration.init = newInit;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return root.toSource();
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
transform as default
|
|
28
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { APIGatewayEvent, Context } from 'aws-lambda'
|
|
2
|
+
|
|
3
|
+
import type { SignatureValidationArgs } from '@cedarjs/storage/UrlSigner'
|
|
4
|
+
|
|
5
|
+
import { urlSigner, fsStorage } from 'src/lib/uploads'
|
|
6
|
+
|
|
7
|
+
export const handler = async (event: APIGatewayEvent, _context: Context) => {
|
|
8
|
+
const fileToReturn = urlSigner.validateSignature(
|
|
9
|
+
event.queryStringParameters as SignatureValidationArgs
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
const { contents, type } = await fsStorage.read(fileToReturn)
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
statusCode: 200,
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': type,
|
|
18
|
+
},
|
|
19
|
+
body: contents,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
__DB_MAX_IMPORT__
|
|
5
|
-
defineStorageTargets,
|
|
6
|
-
defineUploadProfiles,
|
|
7
|
-
} from '@cedarjs/uploads'
|
|
8
|
-
__GET_PATHS_IMPORT__
|
|
9
|
-
__S3_CLIENT__
|
|
10
|
-
/**
|
|
11
|
-
* Where files live. Each target is a provider instance; the Upload row
|
|
12
|
-
* records which target holds a file so it can be read, served, and deleted
|
|
13
|
-
* later. Look one up by name with `resolveTarget(targets, upload.target)`.
|
|
14
|
-
*/
|
|
15
|
-
export const targets = defineStorageTargets({
|
|
16
|
-
__TARGETS__})
|
|
1
|
+
import { createUploadsConfig, setupStorage } from '@cedarjs/storage'
|
|
2
|
+
import { FileSystemStorage } from '@cedarjs/storage/FileSystemStorage'
|
|
3
|
+
import { UrlSigner } from '@cedarjs/storage/UrlSigner'
|
|
17
4
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export const
|
|
24
|
-
|
|
5
|
+
const uploadsConfig = createUploadsConfig({
|
|
6
|
+
// Configure your fields here
|
|
7
|
+
// e.g. modelName: { fields: ['fieldWithUpload']}
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export const fsStorage = new FileSystemStorage({
|
|
11
|
+
baseDir: './uploads',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export const urlSigner = new UrlSigner({
|
|
15
|
+
secret: process.env.UPLOADS_SECRET,
|
|
16
|
+
endpoint: '/signedUrl',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const { saveFiles, storagePrismaExtension } = setupStorage({
|
|
20
|
+
uploadsConfig,
|
|
21
|
+
storageAdapter: fsStorage,
|
|
22
|
+
urlSigner,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export { saveFiles, storagePrismaExtension }
|
|
@@ -1,68 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { recordTelemetryAttributes } from "@cedarjs/cli-helpers/telemetry";
|
|
3
|
-
import { requireTTYOrExit } from "@cedarjs/cli-helpers/tty";
|
|
1
|
+
import { recordTelemetryAttributes } from "@cedarjs/cli-helpers";
|
|
4
2
|
const command = "uploads";
|
|
5
|
-
const description = "
|
|
6
|
-
const TARGET_CHOICES = ["fs", "db", "s3"];
|
|
7
|
-
const TARGET_DESCRIPTIONS = {
|
|
8
|
-
fs: "Local filesystem, uploaded through the api server (development, single-server deploys)",
|
|
9
|
-
db: "Inline in the database, for small files like avatars and thumbnails",
|
|
10
|
-
s3: "S3 or S3-compatible storage, uploaded straight from the browser with presigned URLs"
|
|
11
|
-
};
|
|
3
|
+
const description = "Setup uploads and storage. This will install the required packages and add the required initial configuration to your redwood app.";
|
|
12
4
|
const builder = (yargs) => {
|
|
13
|
-
|
|
14
|
-
choices: TARGET_CHOICES,
|
|
15
|
-
description: "Storage targets to configure: fs (local filesystem), db (inline in the database), s3 (direct-to-S3 presigned uploads). Prompts when omitted.",
|
|
16
|
-
type: "array"
|
|
17
|
-
}).option("force", {
|
|
5
|
+
yargs.option("force", {
|
|
18
6
|
alias: "f",
|
|
19
7
|
default: false,
|
|
20
8
|
description: "Overwrite existing configuration",
|
|
21
9
|
type: "boolean"
|
|
22
10
|
});
|
|
23
11
|
};
|
|
24
|
-
function toTargetChoices(values) {
|
|
25
|
-
const choices = TARGET_CHOICES;
|
|
26
|
-
return [...new Set(values)].filter(
|
|
27
|
-
(t) => choices.includes(t)
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
async function promptForTargets() {
|
|
31
|
-
requireTTYOrExit("--targets");
|
|
32
|
-
const response = await prompts({
|
|
33
|
-
type: "multiselect",
|
|
34
|
-
name: "targets",
|
|
35
|
-
message: "Which storage targets do you want to configure?",
|
|
36
|
-
instructions: false,
|
|
37
|
-
hint: "Space to toggle, enter to confirm",
|
|
38
|
-
min: 1,
|
|
39
|
-
choices: TARGET_CHOICES.map((value) => ({
|
|
40
|
-
title: value,
|
|
41
|
-
description: TARGET_DESCRIPTIONS[value],
|
|
42
|
-
value,
|
|
43
|
-
selected: value !== "s3"
|
|
44
|
-
}))
|
|
45
|
-
});
|
|
46
|
-
if (response.targets === void 0) {
|
|
47
|
-
process.exit(0);
|
|
48
|
-
}
|
|
49
|
-
return toTargetChoices(response.targets);
|
|
50
|
-
}
|
|
51
12
|
const handler = async (options) => {
|
|
52
|
-
const targets = options.targets?.length ? toTargetChoices(options.targets) : await promptForTargets();
|
|
53
13
|
recordTelemetryAttributes({
|
|
54
14
|
command: "setup uploads",
|
|
55
|
-
|
|
56
|
-
|
|
15
|
+
force: options.force,
|
|
16
|
+
skipExamples: options.skipExamples
|
|
57
17
|
});
|
|
58
18
|
const { handler: handler2 } = await import("./uploadsHandler.js");
|
|
59
|
-
return handler2(
|
|
19
|
+
return handler2(options);
|
|
60
20
|
};
|
|
61
21
|
export {
|
|
62
|
-
TARGET_CHOICES,
|
|
63
22
|
builder,
|
|
64
23
|
command,
|
|
65
24
|
description,
|
|
66
|
-
handler
|
|
67
|
-
toTargetChoices
|
|
25
|
+
handler
|
|
68
26
|
};
|
|
@@ -1,265 +1,93 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
4
3
|
import { Listr } from "listr2";
|
|
5
4
|
import { format } from "prettier";
|
|
6
5
|
import { terminalLink } from "termi-link";
|
|
7
|
-
import { colors as c } from "@cedarjs/cli-helpers/colors";
|
|
8
|
-
import { getPrettierOptions } from "@cedarjs/cli-helpers/fileHelpers";
|
|
9
6
|
import {
|
|
10
7
|
addApiPackages,
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
getPrettierOptions,
|
|
9
|
+
colors as c
|
|
10
|
+
} from "@cedarjs/cli-helpers";
|
|
13
11
|
import { formatCedarCommand } from "@cedarjs/cli-helpers/packageManager/display";
|
|
14
|
-
import { addEnvVar } from "@cedarjs/cli-helpers/project";
|
|
15
|
-
import { getSchemaPath } from "@cedarjs/project-config";
|
|
16
12
|
import { errorTelemetry } from "@cedarjs/telemetry";
|
|
17
13
|
import { getPaths, transformTSToJS, writeFile } from "../../../lib/index.js";
|
|
18
14
|
import { isTypeScriptProject } from "../../../lib/project.js";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
import { addUploadModel } from "./schemaPrisma.js";
|
|
22
|
-
import {
|
|
23
|
-
addUploadsPlugin,
|
|
24
|
-
detectServerAuth,
|
|
25
|
-
hasUploadsPlugin
|
|
26
|
-
} from "./serverFile.js";
|
|
27
|
-
const UPPY_VERSION = "^6.0.0";
|
|
28
|
-
const S3_IMPORTS = `import { S3Client } from '@aws-sdk/client-s3'
|
|
29
|
-
|
|
30
|
-
import { createS3Provider } from '@cedarjs/uploads/s3'
|
|
31
|
-
`;
|
|
32
|
-
const S3_CLIENT = `
|
|
33
|
-
// Your own S3 client. It stays available for S3-specific work (lifecycle
|
|
34
|
-
// rules, CORS, and so on) alongside the storage target below. Credentials
|
|
35
|
-
// come from the SDK's default chain: AWS_ACCESS_KEY_ID and
|
|
36
|
-
// AWS_SECRET_ACCESS_KEY in .env, or an IAM role, SSO, or container
|
|
37
|
-
// credentials in production. For an S3-compatible service, add \`endpoint\`
|
|
38
|
-
// and \`forcePathStyle: true\`.
|
|
39
|
-
export const s3Client = new S3Client({
|
|
40
|
-
region: process.env.AWS_REGION,
|
|
41
|
-
})
|
|
42
|
-
`;
|
|
43
|
-
const TARGET_BLOCKS = {
|
|
44
|
-
fs: ` // Files uploaded through the api server and stored on local disk. Good for
|
|
45
|
-
// development and single-server deploys.
|
|
46
|
-
local: createFsProvider({
|
|
47
|
-
uploadDir: path.join(getPaths().api.base, '.uploads'),
|
|
48
|
-
serveBaseUrl: process.env.UPLOAD_SERVE_BASE_URL,
|
|
49
|
-
signSecret: process.env.UPLOAD_TOKEN_SECRET,
|
|
50
|
-
}),
|
|
51
|
-
`,
|
|
52
|
-
db: ` // Small files stored inline in the Upload row (avatars, thumbnails).
|
|
53
|
-
thumbnails: createDbProvider(),
|
|
54
|
-
`,
|
|
55
|
-
s3: ` // Files uploaded straight from the browser to S3 with presigned URLs.
|
|
56
|
-
files: createS3Provider({
|
|
57
|
-
client: s3Client,
|
|
58
|
-
bucket: process.env.S3_BUCKET_UPLOADS,
|
|
59
|
-
region: process.env.AWS_REGION,
|
|
60
|
-
keyPrefix: 'uploads/',
|
|
61
|
-
}),
|
|
62
|
-
`
|
|
63
|
-
};
|
|
64
|
-
const PROFILE_BLOCKS = {
|
|
65
|
-
fs: ` attachment: {
|
|
66
|
-
target: 'local',
|
|
67
|
-
allowedMimeTypes: ['application/pdf', 'image/png', 'image/jpeg'],
|
|
68
|
-
maxFileSize: 25 * 1024 * 1024,
|
|
69
|
-
maxFiles: 10,
|
|
70
|
-
},
|
|
71
|
-
`,
|
|
72
|
-
db: ` avatar: {
|
|
73
|
-
target: 'thumbnails',
|
|
74
|
-
allowedMimeTypes: ['image/png', 'image/jpeg'],
|
|
75
|
-
maxFileSize: DB_MAX_FILE_SIZE,
|
|
76
|
-
maxFiles: 1,
|
|
77
|
-
},
|
|
78
|
-
`,
|
|
79
|
-
s3: ` document: {
|
|
80
|
-
target: 'files',
|
|
81
|
-
allowedMimeTypes: ['application/pdf', 'image/*'],
|
|
82
|
-
maxFileSize: 100 * 1024 * 1024,
|
|
83
|
-
maxFiles: 5,
|
|
84
|
-
},
|
|
85
|
-
`
|
|
86
|
-
};
|
|
87
|
-
const handler = async ({ targets, force }) => {
|
|
88
|
-
const paths = getPaths();
|
|
15
|
+
import { runTransform } from "../../../lib/runTransform.js";
|
|
16
|
+
const handler = async ({ force }) => {
|
|
89
17
|
const projectIsTypescript = isTypeScriptProject();
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const wantsS3 = targets.includes("s3");
|
|
94
|
-
const wantsUppy = wantsFs || wantsS3;
|
|
95
|
-
const packageJson = await import(pathToFileURL(path.join(paths.base, "package.json")).href, { with: { type: "json" } });
|
|
18
|
+
const packageJson = await import(path.join(getPaths().base, "package.json"), {
|
|
19
|
+
with: { type: "json" }
|
|
20
|
+
});
|
|
96
21
|
const cedarVersion = packageJson.default.devDependencies["@cedarjs/core"] ?? "latest";
|
|
97
|
-
const serverFilePath = path.join(paths.api.src, `server.${ext}`);
|
|
98
|
-
const writeTemplateFile = async (templateFile, outputPath, replacements = {}) => {
|
|
99
|
-
const templatePath = path.resolve(
|
|
100
|
-
import.meta.dirname,
|
|
101
|
-
"templates",
|
|
102
|
-
templateFile
|
|
103
|
-
);
|
|
104
|
-
let templateContent = fs.readFileSync(templatePath, "utf-8");
|
|
105
|
-
for (const [token, value] of Object.entries(replacements)) {
|
|
106
|
-
templateContent = templateContent.replaceAll(token, value);
|
|
107
|
-
}
|
|
108
|
-
const content = projectIsTypescript ? templateContent : await transformTSToJS(outputPath, templateContent);
|
|
109
|
-
return writeFile(outputPath, content, { overwriteExisting: force });
|
|
110
|
-
};
|
|
111
|
-
const apiPackages = [`@cedarjs/uploads@${cedarVersion}`];
|
|
112
|
-
const webPackages = [`@cedarjs/uploads@${cedarVersion}`];
|
|
113
|
-
if (wantsS3) {
|
|
114
|
-
apiPackages.push("@aws-sdk/client-s3", "@aws-sdk/s3-request-presigner");
|
|
115
|
-
}
|
|
116
|
-
if (wantsUppy) {
|
|
117
|
-
webPackages.push(
|
|
118
|
-
`@uppy/core@${UPPY_VERSION}`,
|
|
119
|
-
`@uppy/react@${UPPY_VERSION}`,
|
|
120
|
-
`@uppy/dashboard@${UPPY_VERSION}`
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
if (wantsFs) {
|
|
124
|
-
webPackages.push(`@uppy/xhr-upload@${UPPY_VERSION}`);
|
|
125
|
-
}
|
|
126
|
-
if (wantsS3) {
|
|
127
|
-
webPackages.push(`@uppy/aws-s3@${UPPY_VERSION}`);
|
|
128
|
-
}
|
|
129
22
|
const tasks = new Listr(
|
|
130
23
|
[
|
|
131
24
|
{
|
|
132
|
-
|
|
133
|
-
title: "Adding @cedarjs/uploads to your api side..."
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
...addWebPackages(webPackages),
|
|
137
|
-
title: "Adding @cedarjs/uploads to your web side..."
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
title: "Adding the Upload model to schema.prisma...",
|
|
141
|
-
task: async () => {
|
|
142
|
-
const schemaPath = await getSchemaPath(paths.api.prismaConfig);
|
|
143
|
-
const schema = fs.readFileSync(schemaPath, "utf-8");
|
|
144
|
-
fs.writeFileSync(schemaPath, addUploadModel(schema));
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
title: `Adding api/src/lib/uploads.${ext}...`,
|
|
25
|
+
title: `Adding api/src/lib/uploads.${projectIsTypescript ? "ts" : "js"}...`,
|
|
149
26
|
task: async () => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
__S3_IMPORTS__: wantsS3 ? S3_IMPORTS : "",
|
|
155
|
-
__S3_CLIENT__: wantsS3 ? S3_CLIENT : "",
|
|
156
|
-
__FS_IMPORT__: wantsFs ? "createFsProvider," : "",
|
|
157
|
-
__DB_IMPORT__: wantsDb ? "createDbProvider," : "",
|
|
158
|
-
__DB_MAX_IMPORT__: wantsDb ? "DB_MAX_FILE_SIZE," : "",
|
|
159
|
-
__PATH_IMPORT__: wantsFs ? "import path from 'node:path'\n\n" : "",
|
|
160
|
-
__GET_PATHS_IMPORT__: wantsFs ? "import { getPaths } from '@cedarjs/project-config'\n" : "",
|
|
161
|
-
__TARGETS__: targets.map((t) => TARGET_BLOCKS[t]).join("\n"),
|
|
162
|
-
__PROFILES__: targets.map((t) => PROFILE_BLOCKS[t]).join("\n")
|
|
163
|
-
}
|
|
27
|
+
const templatePath = path.resolve(
|
|
28
|
+
import.meta.dirname,
|
|
29
|
+
"templates",
|
|
30
|
+
"srcLibUploads.ts.template"
|
|
164
31
|
);
|
|
32
|
+
const templateContent = fs.readFileSync(templatePath, {
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
flag: "r"
|
|
35
|
+
});
|
|
36
|
+
const uploadsPath = path.join(
|
|
37
|
+
getPaths().api.lib,
|
|
38
|
+
`uploads.${projectIsTypescript ? "ts" : "js"}`
|
|
39
|
+
);
|
|
40
|
+
const uploadsContent = projectIsTypescript ? templateContent : await transformTSToJS(uploadsPath, templateContent);
|
|
41
|
+
return writeFile(uploadsPath, uploadsContent, {
|
|
42
|
+
overwriteExisting: force
|
|
43
|
+
});
|
|
165
44
|
}
|
|
166
45
|
},
|
|
167
46
|
{
|
|
168
|
-
title:
|
|
47
|
+
title: `Adding signedUrl function...`,
|
|
169
48
|
task: async () => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
49
|
+
const templatePath = path.resolve(
|
|
50
|
+
import.meta.dirname,
|
|
51
|
+
"templates",
|
|
52
|
+
"signedUrl.ts.template"
|
|
173
53
|
);
|
|
174
|
-
|
|
175
|
-
"
|
|
176
|
-
|
|
54
|
+
const templateContent = fs.readFileSync(templatePath, {
|
|
55
|
+
encoding: "utf8",
|
|
56
|
+
flag: "r"
|
|
57
|
+
});
|
|
58
|
+
const uploadsPath = path.join(
|
|
59
|
+
getPaths().api.functions,
|
|
60
|
+
`signedUrl.${projectIsTypescript ? "ts" : "js"}`
|
|
177
61
|
);
|
|
62
|
+
const uploadsContent = projectIsTypescript ? templateContent : await transformTSToJS(uploadsPath, templateContent);
|
|
63
|
+
return writeFile(uploadsPath, uploadsContent, {
|
|
64
|
+
overwriteExisting: force
|
|
65
|
+
});
|
|
178
66
|
}
|
|
179
67
|
},
|
|
180
68
|
{
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const directives = ["requireUploadToken", "withSignedUrl"];
|
|
184
|
-
if (wantsDb) {
|
|
185
|
-
directives.push("withDataUri");
|
|
186
|
-
}
|
|
187
|
-
for (const name of directives) {
|
|
188
|
-
await writeTemplateFile(
|
|
189
|
-
`${name}.ts.template`,
|
|
190
|
-
path.join(paths.api.directives, name, `${name}.${ext}`)
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
69
|
+
...addApiPackages([`@cedarjs/storage@${cedarVersion}`]),
|
|
70
|
+
title: "Adding dependencies to your api side..."
|
|
194
71
|
},
|
|
195
72
|
{
|
|
196
|
-
title: "
|
|
197
|
-
task: (
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
title: "Registering the upload plugin in api/src/server...",
|
|
202
|
-
task: async (_ctx, task) => {
|
|
203
|
-
const source = fs.readFileSync(serverFilePath, "utf-8");
|
|
204
|
-
if (hasUploadsPlugin(source)) {
|
|
205
|
-
task.skip("The upload plugin is already registered; skipping.");
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
const graphqlPath = path.join(paths.api.functions, `graphql.${ext}`);
|
|
209
|
-
const authPath = path.join(paths.api.lib, `auth.${ext}`);
|
|
210
|
-
const auth = fs.existsSync(graphqlPath) && fs.existsSync(authPath) && fs.readFileSync(authPath, "utf-8").includes("getCurrentUser") ? detectServerAuth(fs.readFileSync(graphqlPath, "utf-8")) : null;
|
|
211
|
-
let updated;
|
|
212
|
-
try {
|
|
213
|
-
updated = addUploadsPlugin(source, { auth });
|
|
214
|
-
} catch {
|
|
215
|
-
throw new Error(
|
|
216
|
-
`Could not find \`await server.start()\` in ${path.relative(
|
|
217
|
-
paths.base,
|
|
218
|
-
serverFilePath
|
|
219
|
-
)}. Register the plugin by hand before the server starts:
|
|
220
|
-
|
|
221
|
-
import { cedarUploadsPlugin } from '@cedarjs/uploads'
|
|
222
|
-
import { db } from 'src/lib/db'
|
|
223
|
-
import { targets } from 'src/lib/uploads'
|
|
224
|
-
|
|
225
|
-
await server.register(cedarUploadsPlugin, {
|
|
226
|
-
tokenSecret: process.env.UPLOAD_TOKEN_SECRET,
|
|
227
|
-
targets,
|
|
228
|
-
db,
|
|
229
|
-
})
|
|
230
|
-
`
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
fs.writeFileSync(serverFilePath, updated);
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
title: "Adding environment variables to .env...",
|
|
238
|
-
task: () => {
|
|
239
|
-
addEnvVar(
|
|
240
|
-
"UPLOAD_TOKEN_SECRET",
|
|
241
|
-
generateSecret(),
|
|
242
|
-
"Signs upload tokens and file-serving URLs. Keep it secret and use a different value per environment."
|
|
73
|
+
title: "Modifying api/src/lib/db to add uploads prisma extension..",
|
|
74
|
+
task: async () => {
|
|
75
|
+
const dbPath = path.join(
|
|
76
|
+
getPaths().api.lib,
|
|
77
|
+
`db.${projectIsTypescript ? "ts" : "js"}`
|
|
243
78
|
);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
"
|
|
256
|
-
);
|
|
257
|
-
addEnvVar("AWS_ACCESS_KEY_ID", "replace-me", "");
|
|
258
|
-
addEnvVar("AWS_SECRET_ACCESS_KEY", "replace-me", "");
|
|
259
|
-
addEnvVar(
|
|
260
|
-
"S3_BUCKET_UPLOADS",
|
|
261
|
-
"replace-me",
|
|
262
|
-
"Bucket direct uploads land in."
|
|
79
|
+
const transformResult = await runTransform({
|
|
80
|
+
transformPath: path.join(import.meta.dirname, "dbCodemod.js"),
|
|
81
|
+
targetPaths: [dbPath]
|
|
82
|
+
});
|
|
83
|
+
if (transformResult.error) {
|
|
84
|
+
if (transformResult.error === "RW_CODEMOD_ERR_OLD_FORMAT") {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"It looks like your src/lib/db file is using the old format. Please update it as per the v8 upgrade guide: https://cedarjs.com/docs/8.x/upgrade-guides/v8#database-file-structure-change. And run again. \n\nYou can also manually modify your api/src/lib/db to include the prisma extension: https://cedarjs.com/docs/uploads/#attaching-the-prisma-extension"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
throw new Error(
|
|
90
|
+
"Could not add the prisma extension. \n Please modify your api/src/lib/db to include the prisma extension: https://cedarjs.com/docs/uploads/#attaching-the-prisma-extension"
|
|
263
91
|
);
|
|
264
92
|
}
|
|
265
93
|
}
|
|
@@ -268,10 +96,10 @@ const handler = async ({ targets, force }) => {
|
|
|
268
96
|
title: "Prettifying changed files",
|
|
269
97
|
task: async (_ctx, task) => {
|
|
270
98
|
const prettifyPaths = [
|
|
271
|
-
|
|
272
|
-
path.join(
|
|
273
|
-
path.join(
|
|
274
|
-
path.join(
|
|
99
|
+
path.join(getPaths().api.lib, "db.js"),
|
|
100
|
+
path.join(getPaths().api.lib, "db.ts"),
|
|
101
|
+
path.join(getPaths().api.lib, "uploads.js"),
|
|
102
|
+
path.join(getPaths().api.lib, "uploads.ts")
|
|
275
103
|
];
|
|
276
104
|
for (const prettifyPath of prettifyPaths) {
|
|
277
105
|
try {
|
|
@@ -280,11 +108,11 @@ const handler = async ({ targets, force }) => {
|
|
|
280
108
|
}
|
|
281
109
|
const source = fs.readFileSync(prettifyPath, "utf-8");
|
|
282
110
|
const prettierOptions = await getPrettierOptions();
|
|
283
|
-
const
|
|
111
|
+
const prettifiedApp = await format(source, {
|
|
284
112
|
...prettierOptions,
|
|
285
113
|
parser: "babel-ts"
|
|
286
114
|
});
|
|
287
|
-
fs.writeFileSync(prettifyPath,
|
|
115
|
+
fs.writeFileSync(prettifyPath, prettifiedApp, "utf-8");
|
|
288
116
|
} catch {
|
|
289
117
|
task.output = "Couldn't prettify the changes. Please reformat the files manually if needed.";
|
|
290
118
|
}
|
|
@@ -294,30 +122,15 @@ const handler = async ({ targets, force }) => {
|
|
|
294
122
|
{
|
|
295
123
|
title: "One more thing...",
|
|
296
124
|
task: (_ctx, task) => {
|
|
297
|
-
const migrate = c.highlight(
|
|
298
|
-
formatCedarCommand(["prisma", "migrate", "dev"])
|
|
299
|
-
);
|
|
300
|
-
const types = c.highlight(formatCedarCommand(["generate", "types"]));
|
|
301
125
|
task.title = `One more thing...
|
|
302
126
|
|
|
303
|
-
${c.success("
|
|
304
|
-
|
|
305
|
-
Next steps:
|
|
306
|
-
|
|
307
|
-
1. Apply the schema change: ${migrate}
|
|
308
|
-
2. Regenerate types: ${types}
|
|
309
|
-
3. Review the storage targets and upload profiles in api/src/lib/uploads.${ext}${wantsS3 ? "\n 4. Fill in the AWS_* and S3_BUCKET_UPLOADS values in .env" : ""}
|
|
127
|
+
${c.success("\nUploads and storage configured!\n")}
|
|
310
128
|
|
|
311
|
-
|
|
312
|
-
If you add auth later, pass
|
|
313
|
-
\`authenticate: createUploadAuthenticator({ authDecoder, getCurrentUser })\`
|
|
314
|
-
to the plugin in api/src/server.${ext}.
|
|
315
|
-
|
|
316
|
-
Schedule cleanupStaleUploads() from a recurring job to sweep uploads
|
|
317
|
-
that never completed.
|
|
129
|
+
Remember to add UPLOADS_SECRET to your .env file. You can generate one with ${c.highlight(formatCedarCommand(["generate", "secret"]))}
|
|
318
130
|
|
|
319
131
|
Check out the docs for more info:
|
|
320
132
|
${terminalLink("", "https://cedarjs.com/docs/uploads")}
|
|
133
|
+
|
|
321
134
|
`;
|
|
322
135
|
}
|
|
323
136
|
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -16,12 +16,11 @@ import * as setupNeon from "./setup/neon/neon.js";
|
|
|
16
16
|
import * as setupPackage from "./setup/package/package.js";
|
|
17
17
|
import * as setupRealtime from "./setup/realtime/realtime.js";
|
|
18
18
|
import * as setupServerFile from "./setup/server-file/serverFile.js";
|
|
19
|
-
import * as setupTenancy from "./setup/tenancy/tenancy.js";
|
|
20
19
|
import * as setupUi from "./setup/ui/ui.js";
|
|
21
20
|
import * as setupUploads from "./setup/uploads/uploads.js";
|
|
22
21
|
const command = "setup <command>";
|
|
23
22
|
const description = "Initialize project config and install packages";
|
|
24
|
-
const builder = (yargs) => yargs.command(setupAuth).command(setupCache).command(setupDatabase).command(setupDeploy).command(setupDocker).command(setupGenerator).command(setupGraphql).command(setupI18n).command(setupJobs).command(setupMailer).command(setupMiddleware).command(setupMonitoring).command(setupNeon).command(setupPackage).command(setupRealtime).command(setupServerFile).command(
|
|
23
|
+
const builder = (yargs) => yargs.command(setupAuth).command(setupCache).command(setupDatabase).command(setupDeploy).command(setupDocker).command(setupGenerator).command(setupGraphql).command(setupI18n).command(setupJobs).command(setupMailer).command(setupMiddleware).command(setupMonitoring).command(setupNeon).command(setupPackage).command(setupRealtime).command(setupServerFile).command(setupUi).command(setupUploads).demandCommand().middleware(detectCedarVersion).epilogue(
|
|
25
24
|
`Also see the ${terminalLink(
|
|
26
25
|
"CedarJS CLI Reference",
|
|
27
26
|
"https://cedarjs.com/docs/cli-commands#setup"
|
package/dist/commands/studio.js
CHANGED