alchemy 0.37.1 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/alchemy.mjs +44306 -48055
- package/bin/alchemy.ts +80 -85
- package/bin/commands/create.ts +263 -0
- package/bin/constants.ts +13 -0
- package/bin/errors.ts +11 -0
- package/bin/services/dependencies.ts +47 -0
- package/bin/services/get-package-version.ts +11 -0
- package/bin/services/package-manager.ts +113 -0
- package/bin/services/template-manager.ts +137 -0
- package/bin/types.ts +83 -0
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +12 -3
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.d.ts +2 -0
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +47 -12
- package/lib/apply.js.map +1 -1
- package/lib/build-date.d.ts +6 -0
- package/lib/build-date.d.ts.map +1 -0
- package/lib/build-date.js +8 -0
- package/lib/build-date.js.map +1 -0
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +2 -1
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/do-state-store/internal.d.ts.map +1 -1
- package/lib/cloudflare/do-state-store/internal.js +9 -1
- package/lib/cloudflare/do-state-store/internal.js.map +1 -1
- package/lib/cloudflare/pipeline.d.ts +1 -1
- package/lib/cloudflare/pipeline.d.ts.map +1 -1
- package/lib/cloudflare/pipeline.js +6 -1
- package/lib/cloudflare/pipeline.js.map +1 -1
- package/lib/cloudflare/worker-stub.d.ts +23 -2
- package/lib/cloudflare/worker-stub.d.ts.map +1 -1
- package/lib/cloudflare/worker-stub.js +16 -2
- package/lib/cloudflare/worker-stub.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +4 -3
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +2 -1
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/context.d.ts +2 -2
- package/lib/context.d.ts.map +1 -1
- package/lib/destroy.d.ts +8 -2
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +19 -5
- package/lib/destroy.js.map +1 -1
- package/lib/scope.d.ts +26 -4
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +104 -9
- package/lib/scope.js.map +1 -1
- package/lib/sentry/project.js +1 -1
- package/lib/sentry/project.js.map +1 -1
- package/lib/util/exists.d.ts +2 -0
- package/lib/util/exists.d.ts.map +1 -0
- package/lib/util/exists.js +11 -0
- package/lib/util/exists.js.map +1 -0
- package/lib/util/logger.js +1 -1
- package/lib/util/logger.js.map +1 -1
- package/lib/util/mutex.d.ts +9 -0
- package/lib/util/mutex.d.ts.map +1 -0
- package/lib/util/mutex.js +35 -0
- package/lib/util/mutex.js.map +1 -0
- package/package.json +16 -6
- package/src/alchemy.ts +17 -5
- package/src/apply.ts +67 -17
- package/src/build-date.ts +8 -0
- package/src/cloudflare/api.ts +2 -1
- package/src/cloudflare/do-state-store/internal.ts +10 -1
- package/src/cloudflare/pipeline.ts +6 -0
- package/src/cloudflare/worker-stub.ts +31 -2
- package/src/cloudflare/worker.ts +4 -3
- package/src/context.ts +2 -2
- package/src/destroy.ts +30 -7
- package/src/scope.ts +145 -10
- package/src/sentry/project.ts +1 -1
- package/src/util/exists.ts +10 -0
- package/src/util/logger.ts +1 -1
- package/src/util/mutex.ts +33 -0
- package/templates/astro/.vscode/extensions.json +4 -0
- package/templates/astro/.vscode/launch.json +11 -0
- package/templates/astro/README.md +48 -0
- package/templates/astro/_env.example +1 -0
- package/templates/astro/_gitignore +27 -0
- package/templates/astro/alchemy.run.ts +16 -0
- package/templates/astro/astro.config.mjs +8 -0
- package/templates/astro/package.json +23 -0
- package/templates/astro/public/favicon.svg +9 -0
- package/templates/astro/src/assets/astro.svg +1 -0
- package/templates/astro/src/assets/background.svg +1 -0
- package/templates/astro/src/components/Welcome.astro +210 -0
- package/templates/astro/src/layouts/Layout.astro +22 -0
- package/templates/astro/src/pages/api/hello.ts +19 -0
- package/templates/astro/src/pages/index.astro +11 -0
- package/templates/astro/tsconfig.json +18 -0
- package/templates/astro/types/env.d.ts +16 -0
- package/templates/astro/wrangler.jsonc +6 -0
- package/templates/nuxt/README.md +75 -0
- package/templates/nuxt/_env +1 -0
- package/templates/nuxt/_env.example +1 -0
- package/templates/nuxt/_gitignore +27 -0
- package/templates/nuxt/alchemy.run.ts +16 -0
- package/templates/nuxt/app.vue +6 -0
- package/templates/nuxt/nuxt.config.ts +13 -0
- package/templates/nuxt/package.json +26 -0
- package/templates/nuxt/public/favicon.ico +0 -0
- package/templates/nuxt/public/robots.txt +2 -0
- package/templates/nuxt/server/api/hello.ts +7 -0
- package/templates/nuxt/server/middleware/auth.ts +5 -0
- package/templates/nuxt/server/middleware/hello.ts +5 -0
- package/templates/nuxt/server/tsconfig.json +3 -0
- package/templates/nuxt/tsconfig.json +15 -0
- package/templates/nuxt/types/env.d.ts +16 -0
- package/templates/react-router/.vscode/settings.json +5 -0
- package/templates/react-router/README.md +79 -0
- package/templates/react-router/_env +1 -0
- package/templates/react-router/_env.example +1 -0
- package/templates/react-router/_gitignore +15 -0
- package/templates/react-router/alchemy.run.ts +17 -0
- package/templates/react-router/app/app.css +15 -0
- package/templates/react-router/app/entry.server.tsx +43 -0
- package/templates/react-router/app/root.tsx +75 -0
- package/templates/react-router/app/routes/home.tsx +17 -0
- package/templates/react-router/app/routes.ts +3 -0
- package/templates/react-router/app/welcome/logo-dark.svg +23 -0
- package/templates/react-router/app/welcome/logo-light.svg +23 -0
- package/templates/react-router/app/welcome/welcome.tsx +90 -0
- package/templates/react-router/package.json +34 -0
- package/templates/react-router/public/favicon.ico +0 -0
- package/templates/react-router/react-router.config.ts +8 -0
- package/templates/react-router/tsconfig.cloudflare.json +27 -0
- package/templates/react-router/tsconfig.json +17 -0
- package/templates/react-router/tsconfig.node.json +20 -0
- package/templates/react-router/types/env.d.ts +16 -0
- package/templates/react-router/vite.config.ts +14 -0
- package/templates/react-router/workers/app.ts +23 -0
- package/templates/react-router/wrangler.jsonc +6 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-interruptors.mdc +247 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-middleware.mdc +75 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-react.mdc +110 -0
- package/templates/rwsdk/.cursor/rules/rwsdk_rwsdk-request-response.mdc +213 -0
- package/templates/rwsdk/.devcontainer/Dockerfile +8 -0
- package/templates/rwsdk/.devcontainer/devcontainer.json +21 -0
- package/templates/rwsdk/README.md +62 -0
- package/templates/rwsdk/_env +5 -0
- package/templates/rwsdk/_env.example +5 -0
- package/templates/rwsdk/_gitignore +71 -0
- package/templates/rwsdk/alchemy.run.ts +29 -0
- package/templates/rwsdk/package.json +61 -0
- package/templates/rwsdk/prisma/schema.prisma +43 -0
- package/templates/rwsdk/src/app/Document.tsx +16 -0
- package/templates/rwsdk/src/app/headers.ts +32 -0
- package/templates/rwsdk/src/app/pages/Home.tsx +13 -0
- package/templates/rwsdk/src/app/pages/user/Login.tsx +79 -0
- package/templates/rwsdk/src/app/pages/user/functions.ts +171 -0
- package/templates/rwsdk/src/app/pages/user/routes.ts +17 -0
- package/templates/rwsdk/src/app/shared/links.ts +3 -0
- package/templates/rwsdk/src/client.tsx +3 -0
- package/templates/rwsdk/src/db.ts +26 -0
- package/templates/rwsdk/src/scripts/seed.ts +21 -0
- package/templates/rwsdk/src/session/durableObject.ts +63 -0
- package/templates/rwsdk/src/session/store.ts +13 -0
- package/templates/rwsdk/src/worker.tsx +63 -0
- package/templates/rwsdk/tsconfig.json +47 -0
- package/templates/rwsdk/types/env.d.ts +16 -0
- package/templates/rwsdk/types/rw.d.ts +5 -0
- package/templates/rwsdk/types/vite.d.ts +4 -0
- package/templates/rwsdk/vite.config.mts +6 -0
- package/templates/rwsdk/wrangler.jsonc +28 -0
- package/templates/sveltekit/README.md +38 -0
- package/templates/sveltekit/_env +1 -0
- package/templates/sveltekit/_env.example +1 -0
- package/templates/sveltekit/_gitignore +26 -0
- package/templates/sveltekit/_npmrc +1 -0
- package/templates/sveltekit/_prettierrc +3 -0
- package/templates/sveltekit/alchemy.run.ts +16 -0
- package/templates/sveltekit/bun.lock +1099 -0
- package/templates/sveltekit/package.json +33 -0
- package/templates/sveltekit/src/app.css +1 -0
- package/templates/sveltekit/src/app.d.ts +13 -0
- package/templates/sveltekit/src/app.html +12 -0
- package/templates/sveltekit/src/routes/+layout.svelte +7 -0
- package/templates/sveltekit/src/routes/+page.svelte +2 -0
- package/templates/sveltekit/static/favicon.png +0 -0
- package/templates/sveltekit/svelte.config.js +12 -0
- package/templates/sveltekit/tsconfig.json +27 -0
- package/templates/sveltekit/types/env.d.ts +16 -0
- package/templates/sveltekit/vite.config.ts +6 -0
- package/templates/sveltekit/wrangler.jsonc +7 -0
- package/templates/tanstack-start/.vscode/settings.json +11 -0
- package/templates/tanstack-start/README.md +72 -0
- package/templates/tanstack-start/_env +1 -0
- package/templates/tanstack-start/_env.example +1 -0
- package/templates/tanstack-start/_gitignore +22 -0
- package/templates/tanstack-start/_prettierignore +4 -0
- package/templates/tanstack-start/alchemy.run.ts +16 -0
- package/templates/tanstack-start/package.json +37 -0
- package/templates/tanstack-start/public/android-chrome-192x192.png +0 -0
- package/templates/tanstack-start/public/android-chrome-512x512.png +0 -0
- package/templates/tanstack-start/public/apple-touch-icon.png +0 -0
- package/templates/tanstack-start/public/favicon-16x16.png +0 -0
- package/templates/tanstack-start/public/favicon-32x32.png +0 -0
- package/templates/tanstack-start/public/favicon.ico +0 -0
- package/templates/tanstack-start/public/favicon.png +0 -0
- package/templates/tanstack-start/public/site.webmanifest +19 -0
- package/templates/tanstack-start/src/components/DefaultCatchBoundary.tsx +53 -0
- package/templates/tanstack-start/src/components/NotFound.tsx +25 -0
- package/templates/tanstack-start/src/components/PostError.tsx +5 -0
- package/templates/tanstack-start/src/components/UserError.tsx +5 -0
- package/templates/tanstack-start/src/router.tsx +22 -0
- package/templates/tanstack-start/src/routes/__root.tsx +146 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +10 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +10 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout.tsx +34 -0
- package/templates/tanstack-start/src/routes/_pathlessLayout.tsx +16 -0
- package/templates/tanstack-start/src/routes/api/users.$userId.ts +28 -0
- package/templates/tanstack-start/src/routes/api/users.ts +59 -0
- package/templates/tanstack-start/src/routes/customScript[.]js.ts +10 -0
- package/templates/tanstack-start/src/routes/deferred.tsx +62 -0
- package/templates/tanstack-start/src/routes/index.tsx +12 -0
- package/templates/tanstack-start/src/routes/posts.$postId.tsx +34 -0
- package/templates/tanstack-start/src/routes/posts.index.tsx +8 -0
- package/templates/tanstack-start/src/routes/posts.tsx +38 -0
- package/templates/tanstack-start/src/routes/posts_.$postId.deep.tsx +29 -0
- package/templates/tanstack-start/src/routes/redirect.tsx +9 -0
- package/templates/tanstack-start/src/routes/users.$userId.tsx +44 -0
- package/templates/tanstack-start/src/routes/users.index.tsx +18 -0
- package/templates/tanstack-start/src/routes/users.tsx +49 -0
- package/templates/tanstack-start/src/styles/app.css +12 -0
- package/templates/tanstack-start/src/utils/loggingMiddleware.tsx +41 -0
- package/templates/tanstack-start/src/utils/posts.tsx +40 -0
- package/templates/tanstack-start/src/utils/seo.ts +33 -0
- package/templates/tanstack-start/src/utils/users.tsx +5 -0
- package/templates/tanstack-start/tsconfig.json +31 -0
- package/templates/tanstack-start/types/env.d.ts +16 -0
- package/templates/tanstack-start/vite.config.ts +33 -0
- package/templates/tanstack-start/wrangler.jsonc +7 -0
- package/templates/typescript/README.md +15 -0
- package/templates/typescript/_gitignore +37 -0
- package/templates/typescript/alchemy.run.ts +15 -0
- package/templates/typescript/index.ts +1 -0
- package/templates/typescript/package.json +18 -0
- package/templates/typescript/src/worker.js +5 -0
- package/templates/typescript/src/worker.ts +7 -0
- package/templates/typescript/tsconfig.json +21 -0
- package/templates/typescript/types/env.d.ts +16 -0
- package/templates/vite/README.md +54 -0
- package/templates/vite/_env.example +1 -0
- package/templates/vite/_gitignore +27 -0
- package/templates/vite/alchemy.run.ts +17 -0
- package/templates/vite/eslint.config.js +28 -0
- package/templates/vite/index.html +13 -0
- package/templates/vite/package.json +35 -0
- package/templates/vite/public/vite.svg +1 -0
- package/templates/vite/src/App.css +42 -0
- package/templates/vite/src/App.tsx +35 -0
- package/templates/vite/src/assets/react.svg +1 -0
- package/templates/vite/src/index.css +68 -0
- package/templates/vite/src/main.tsx +10 -0
- package/templates/vite/src/vite-env.d.ts +1 -0
- package/templates/vite/tsconfig.json +34 -0
- package/templates/vite/types/env.d.ts +16 -0
- package/templates/vite/vite.config.ts +9 -0
- package/templates/vite/worker/index.ts +12 -0
- package/templates/vite/wrangler.jsonc +6 -0
- package/bin/create-alchemy.ts +0 -1220
package/bin/alchemy.ts
CHANGED
|
@@ -1,93 +1,88 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseArgs } from "node:util";
|
|
3
|
-
import { bootstrapS3 } from "./bootstrap-s3.ts";
|
|
4
|
-
import { createAlchemy } from "./create-alchemy.ts";
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
help: { type: "boolean", short: "h" },
|
|
16
|
-
version: { type: "boolean", short: "v" },
|
|
17
|
-
region: { type: "string" },
|
|
18
|
-
prefix: { type: "string" },
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// First positional is the sub-command (e.g. `create`)
|
|
23
|
-
const command = positionals.shift();
|
|
24
|
-
|
|
25
|
-
const usage = `Usage: alchemy <command> [options]
|
|
26
|
-
|
|
27
|
-
Available commands:
|
|
28
|
-
create Scaffold a new project
|
|
29
|
-
bootstrap Bootstrap cloud resources for alchemy
|
|
30
|
-
|
|
31
|
-
Bootstrap options:
|
|
32
|
-
--region AWS region (defaults to AWS profile default)
|
|
33
|
-
--prefix S3 bucket name prefix (default: alchemy-state)
|
|
34
|
-
`;
|
|
3
|
+
import { createCli, trpcServer, zod as z } from "trpc-cli";
|
|
4
|
+
import { createAlchemy } from "./commands/create.ts";
|
|
5
|
+
import { getPackageVersion } from "./services/get-package-version.ts";
|
|
6
|
+
import {
|
|
7
|
+
PackageManagerSchema,
|
|
8
|
+
ProjectNameSchema,
|
|
9
|
+
TemplateSchema,
|
|
10
|
+
type CreateInput,
|
|
11
|
+
} from "./types.ts";
|
|
35
12
|
|
|
36
|
-
|
|
37
|
-
console.error(usage);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
13
|
+
const t = trpcServer.initTRPC.create();
|
|
40
14
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
15
|
+
const router = t.router({
|
|
16
|
+
create: t.procedure
|
|
17
|
+
.meta({
|
|
18
|
+
description: "Create a new Alchemy project",
|
|
19
|
+
})
|
|
20
|
+
.input(
|
|
21
|
+
z.tuple([
|
|
22
|
+
ProjectNameSchema.optional(),
|
|
23
|
+
z
|
|
24
|
+
.object({
|
|
25
|
+
template: TemplateSchema.optional(),
|
|
26
|
+
packageManager: PackageManagerSchema.optional(),
|
|
27
|
+
yes: z
|
|
28
|
+
.boolean()
|
|
29
|
+
.optional()
|
|
30
|
+
.default(false)
|
|
31
|
+
.describe("Skip prompts and use defaults"),
|
|
32
|
+
overwrite: z
|
|
33
|
+
.boolean()
|
|
34
|
+
.optional()
|
|
35
|
+
.default(false)
|
|
36
|
+
.describe("Overwrite existing directory"),
|
|
37
|
+
bun: z
|
|
38
|
+
.boolean()
|
|
39
|
+
.optional()
|
|
40
|
+
.default(false)
|
|
41
|
+
.describe("Use Bun as the package manager"),
|
|
42
|
+
npm: z
|
|
43
|
+
.boolean()
|
|
44
|
+
.optional()
|
|
45
|
+
.default(false)
|
|
46
|
+
.describe("Use npm as the package manager"),
|
|
47
|
+
pnpm: z
|
|
48
|
+
.boolean()
|
|
49
|
+
.optional()
|
|
50
|
+
.default(false)
|
|
51
|
+
.describe("Use pnpm as the package manager"),
|
|
52
|
+
yarn: z
|
|
53
|
+
.boolean()
|
|
54
|
+
.optional()
|
|
55
|
+
.default(false)
|
|
56
|
+
.describe("Use Yarn as the package manager"),
|
|
57
|
+
install: z
|
|
58
|
+
.boolean()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("Install dependencies after scaffolding"),
|
|
61
|
+
})
|
|
62
|
+
.optional()
|
|
63
|
+
.default({}),
|
|
64
|
+
]),
|
|
65
|
+
)
|
|
66
|
+
.mutation(async ({ input }) => {
|
|
67
|
+
const [name, options] = input;
|
|
68
|
+
const isTest = process.env.NODE_ENV === "test";
|
|
69
|
+
const combinedInput: CreateInput = {
|
|
51
70
|
name,
|
|
52
|
-
|
|
53
|
-
yes:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
await createAlchemy({
|
|
62
|
-
name,
|
|
63
|
-
template: values.template as string | undefined,
|
|
64
|
-
yes: values.yes as boolean | undefined,
|
|
65
|
-
overwrite: values.overwrite as boolean | undefined,
|
|
66
|
-
help: values.help as boolean | undefined,
|
|
67
|
-
version: values.version as boolean | undefined,
|
|
68
|
-
});
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
+
...options,
|
|
72
|
+
yes: isTest || options.yes,
|
|
73
|
+
};
|
|
74
|
+
await createAlchemy(combinedInput);
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
const subcommand = positionals.shift();
|
|
78
|
+
export type AppRouter = typeof router;
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
console.error(`Unknown bootstrap subcommand: ${subcommand || "(none)"}`);
|
|
83
|
-
console.error("Available bootstrap subcommands:");
|
|
84
|
-
console.error(" s3 Create S3 bucket for state storage");
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
80
|
+
const cli = createCli({
|
|
81
|
+
router,
|
|
82
|
+
name: "alchemy",
|
|
83
|
+
version: getPackageVersion(),
|
|
84
|
+
description:
|
|
85
|
+
"🧪 Welcome to Alchemy! Creating infrastructure as code with JavaScript and TypeScript.",
|
|
86
|
+
});
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
console.error(`Unknown command: ${command}`);
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
88
|
+
cli.run();
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cancel,
|
|
3
|
+
confirm,
|
|
4
|
+
intro,
|
|
5
|
+
isCancel,
|
|
6
|
+
log,
|
|
7
|
+
note,
|
|
8
|
+
outro,
|
|
9
|
+
select,
|
|
10
|
+
spinner,
|
|
11
|
+
text,
|
|
12
|
+
} from "@clack/prompts";
|
|
13
|
+
import * as fs from "fs-extra";
|
|
14
|
+
import { existsSync } from "node:fs";
|
|
15
|
+
import { join, resolve } from "node:path";
|
|
16
|
+
import pc from "picocolors";
|
|
17
|
+
|
|
18
|
+
import { throwWithContext } from "../errors.ts";
|
|
19
|
+
import { detectPackageManager } from "../services/package-manager.ts";
|
|
20
|
+
import { copyTemplate } from "../services/template-manager.ts";
|
|
21
|
+
import type { CreateInput, ProjectContext, TemplateType } from "../types.ts";
|
|
22
|
+
import { ProjectNameSchema, TEMPLATE_DEFINITIONS } from "../types.ts";
|
|
23
|
+
|
|
24
|
+
const isTest = process.env.NODE_ENV === "test";
|
|
25
|
+
|
|
26
|
+
async function createProjectContext(
|
|
27
|
+
cliOptions: CreateInput,
|
|
28
|
+
): Promise<ProjectContext> {
|
|
29
|
+
const detectedPm = detectPackageManager();
|
|
30
|
+
const options = { yes: isTest, ...cliOptions };
|
|
31
|
+
|
|
32
|
+
let name: string;
|
|
33
|
+
if (options.name) {
|
|
34
|
+
const result = ProjectNameSchema.safeParse(options.name);
|
|
35
|
+
if (!result.success) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Invalid project name: ${result.error.errors[0]?.message}`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
name = options.name;
|
|
41
|
+
log.info(`Using project name: ${pc.yellow(name)}`);
|
|
42
|
+
} else {
|
|
43
|
+
const nameResult = await text({
|
|
44
|
+
message: "What is your project name?",
|
|
45
|
+
placeholder: "my-alchemy-app",
|
|
46
|
+
validate: (value) => {
|
|
47
|
+
const result = ProjectNameSchema.safeParse(value);
|
|
48
|
+
return result.success ? undefined : result.error.errors[0]?.message;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (isCancel(nameResult)) {
|
|
53
|
+
cancel(pc.red("Operation cancelled."));
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
name = nameResult;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let selectedTemplate: TemplateType;
|
|
61
|
+
if (options.template) {
|
|
62
|
+
selectedTemplate = options.template;
|
|
63
|
+
log.info(`Using template: ${pc.yellow(selectedTemplate)}`);
|
|
64
|
+
} else {
|
|
65
|
+
const templateResult = await select({
|
|
66
|
+
message: "Which template would you like to use?",
|
|
67
|
+
options: TEMPLATE_DEFINITIONS.map((t) => ({
|
|
68
|
+
label: t.description,
|
|
69
|
+
value: t.name as TemplateType,
|
|
70
|
+
})),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (isCancel(templateResult)) {
|
|
74
|
+
cancel(pc.red("Operation cancelled."));
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
selectedTemplate = templateResult;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const templateDefinition = TEMPLATE_DEFINITIONS.find(
|
|
82
|
+
(t) => t.name === selectedTemplate,
|
|
83
|
+
);
|
|
84
|
+
if (!templateDefinition) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Template '${pc.yellow(selectedTemplate)}' not found. Available templates: ${TEMPLATE_DEFINITIONS.map((t) => pc.cyan(t.name)).join(", ")}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const path = resolve(process.cwd(), name);
|
|
91
|
+
let packageManager = options.packageManager || detectedPm;
|
|
92
|
+
|
|
93
|
+
// Override package manager if specific flags are provided
|
|
94
|
+
if (options.bun) packageManager = "bun";
|
|
95
|
+
else if (options.npm) packageManager = "npm";
|
|
96
|
+
else if (options.pnpm) packageManager = "pnpm";
|
|
97
|
+
else if (options.yarn) packageManager = "yarn";
|
|
98
|
+
|
|
99
|
+
let shouldInstall = true;
|
|
100
|
+
if (options.install !== undefined) {
|
|
101
|
+
shouldInstall = options.install;
|
|
102
|
+
log.info(
|
|
103
|
+
`Dependencies installation: ${pc.yellow(shouldInstall ? "enabled" : "disabled")}`,
|
|
104
|
+
);
|
|
105
|
+
} else if (!options.yes) {
|
|
106
|
+
const installResult = await confirm({
|
|
107
|
+
message: "Install dependencies?",
|
|
108
|
+
initialValue: true,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (isCancel(installResult)) {
|
|
112
|
+
cancel(pc.red("Operation cancelled."));
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
shouldInstall = installResult;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
name,
|
|
121
|
+
path,
|
|
122
|
+
template: selectedTemplate,
|
|
123
|
+
packageManager,
|
|
124
|
+
isTest,
|
|
125
|
+
options: {
|
|
126
|
+
...options,
|
|
127
|
+
install: shouldInstall,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function handleDirectoryOverwrite(
|
|
133
|
+
context: ProjectContext,
|
|
134
|
+
): Promise<void> {
|
|
135
|
+
if (!existsSync(context.path)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let shouldOverwrite = false;
|
|
140
|
+
|
|
141
|
+
if (context.options.overwrite) {
|
|
142
|
+
shouldOverwrite = true;
|
|
143
|
+
log.warn(
|
|
144
|
+
`Directory ${pc.yellow(context.name)} already exists. Overwriting due to ${pc.cyan("--overwrite")} flag.`,
|
|
145
|
+
);
|
|
146
|
+
} else {
|
|
147
|
+
const overwriteResult = await confirm({
|
|
148
|
+
message: `Directory ${pc.yellow(context.name)} already exists. Overwrite?`,
|
|
149
|
+
initialValue: false,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (isCancel(overwriteResult)) {
|
|
153
|
+
cancel(pc.red("Operation cancelled."));
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
shouldOverwrite = overwriteResult;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!shouldOverwrite) {
|
|
161
|
+
cancel(pc.red("Operation cancelled."));
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const s = spinner();
|
|
166
|
+
s.start(`Removing existing directory: ${pc.yellow(context.path)}`);
|
|
167
|
+
try {
|
|
168
|
+
await fs.rm(context.path, { recursive: true, force: true });
|
|
169
|
+
s.stop(`Directory ${pc.yellow(context.path)} removed.`);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
s.stop(pc.red(`Failed to remove directory ${pc.yellow(context.path)}.`));
|
|
172
|
+
throwWithContext(error, "Directory removal failed");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function initializeTemplate(context: ProjectContext): Promise<void> {
|
|
177
|
+
const templateDefinition = TEMPLATE_DEFINITIONS.find(
|
|
178
|
+
(t) => t.name === context.template,
|
|
179
|
+
);
|
|
180
|
+
if (!templateDefinition) {
|
|
181
|
+
throw new Error(`Template definition not found for: ${context.template}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
await copyTemplate(context.template, context);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
throwWithContext(
|
|
188
|
+
error,
|
|
189
|
+
`Template initialization failed for '${context.template}'`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Create .gitignore if it doesn't exist
|
|
194
|
+
const gitignorePath = join(context.path, ".gitignore");
|
|
195
|
+
if (!existsSync(gitignorePath)) {
|
|
196
|
+
try {
|
|
197
|
+
await fs.writeFile(
|
|
198
|
+
gitignorePath,
|
|
199
|
+
"node_modules/\n.env\n.env.local\ndist/\nlib/\n.wrangler/\nwrangler.jsonc\n*.tsbuildinfo\n",
|
|
200
|
+
);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
203
|
+
log.warn(`Failed to create .gitignore: ${errorMsg}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function createAlchemy(cliOptions: CreateInput): Promise<void> {
|
|
209
|
+
try {
|
|
210
|
+
intro(pc.cyan("🧪 Welcome to Alchemy!"));
|
|
211
|
+
log.info("Creating a new Alchemy project...");
|
|
212
|
+
|
|
213
|
+
const context = await createProjectContext(cliOptions);
|
|
214
|
+
|
|
215
|
+
log.info(`Detected package manager: ${pc.green(context.packageManager)}`);
|
|
216
|
+
|
|
217
|
+
await handleDirectoryOverwrite(context);
|
|
218
|
+
|
|
219
|
+
await initializeTemplate(context);
|
|
220
|
+
|
|
221
|
+
const installInstructions =
|
|
222
|
+
context.options.install === false
|
|
223
|
+
? `
|
|
224
|
+
${pc.cyan("📦 Install dependencies:")}
|
|
225
|
+
cd ${context.name}
|
|
226
|
+
${context.packageManager} install
|
|
227
|
+
|
|
228
|
+
`
|
|
229
|
+
: "";
|
|
230
|
+
|
|
231
|
+
note(
|
|
232
|
+
`
|
|
233
|
+
${pc.cyan("📁 Navigate to your project:")}
|
|
234
|
+
cd ${context.name}
|
|
235
|
+
|
|
236
|
+
${installInstructions}${pc.cyan("🚀 Deploy your project:")}
|
|
237
|
+
${context.packageManager} run deploy
|
|
238
|
+
|
|
239
|
+
${pc.cyan("🧹 Destroy your project:")}
|
|
240
|
+
${context.packageManager} run destroy
|
|
241
|
+
|
|
242
|
+
${pc.cyan("📚 Learn more:")}
|
|
243
|
+
https://alchemy.run
|
|
244
|
+
`,
|
|
245
|
+
"Next Steps:",
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
outro(
|
|
249
|
+
pc.green(`✅ Project ${pc.yellow(context.name)} created successfully!`),
|
|
250
|
+
);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
log.error("An unexpected error occurred:");
|
|
253
|
+
if (error instanceof Error) {
|
|
254
|
+
log.error(`${pc.red("Error:")} ${error.message}`);
|
|
255
|
+
if (error.stack && process.env.DEBUG) {
|
|
256
|
+
log.error(`${pc.gray("Stack trace:")}\n${error.stack}`);
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
log.error(pc.red(String(error)));
|
|
260
|
+
}
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
}
|
package/bin/constants.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { getPackageVersion } from "./services/get-package-version.ts";
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const distPath = path.dirname(__filename);
|
|
7
|
+
export const PKG_ROOT = path.join(distPath, "../");
|
|
8
|
+
|
|
9
|
+
export const dependencyVersionMap = {
|
|
10
|
+
alchemy: getPackageVersion(),
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export type DependencyVersionMap = keyof typeof dependencyVersionMap;
|
package/bin/errors.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { log } from "@clack/prompts";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
|
|
4
|
+
export function throwWithContext(error: unknown, context: string): never {
|
|
5
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
6
|
+
log.error(pc.red(`❌ ${context}`));
|
|
7
|
+
log.error(pc.gray(` ${errorMsg}`));
|
|
8
|
+
throw new Error(`${context}: ${errorMsg}`, {
|
|
9
|
+
cause: error instanceof Error ? error : new Error(String(error)),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { log } from "@clack/prompts";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type DependencyVersionMap,
|
|
7
|
+
dependencyVersionMap,
|
|
8
|
+
} from "../constants.ts";
|
|
9
|
+
|
|
10
|
+
export const addPackageDependencies = async (opts: {
|
|
11
|
+
dependencies?: DependencyVersionMap[];
|
|
12
|
+
devDependencies?: DependencyVersionMap[];
|
|
13
|
+
projectDir: string;
|
|
14
|
+
}): Promise<void> => {
|
|
15
|
+
const { dependencies = [], devDependencies = [], projectDir } = opts;
|
|
16
|
+
|
|
17
|
+
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
18
|
+
|
|
19
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
20
|
+
|
|
21
|
+
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
22
|
+
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
|
|
23
|
+
|
|
24
|
+
for (const pkgName of dependencies) {
|
|
25
|
+
const version = dependencyVersionMap[pkgName];
|
|
26
|
+
if (version) {
|
|
27
|
+
pkgJson.dependencies[pkgName] = version;
|
|
28
|
+
} else {
|
|
29
|
+
log.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const pkgName of devDependencies) {
|
|
34
|
+
const version = dependencyVersionMap[pkgName];
|
|
35
|
+
if (version) {
|
|
36
|
+
pkgJson.devDependencies[pkgName] = version;
|
|
37
|
+
} else {
|
|
38
|
+
log.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await fs.writeJson(pkgJsonPath, pkgJson, {
|
|
43
|
+
spaces: 2,
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const addPackageDependency = addPackageDependencies;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { PKG_ROOT } from "../constants.ts";
|
|
4
|
+
|
|
5
|
+
export const getPackageVersion = () => {
|
|
6
|
+
const packageJsonPath = path.join(PKG_ROOT, "package.json");
|
|
7
|
+
|
|
8
|
+
const packageJsonContent = fs.readJSONSync(packageJsonPath);
|
|
9
|
+
|
|
10
|
+
return packageJsonContent.version ?? "1.0.0";
|
|
11
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { log } from "@clack/prompts";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import * as fs from "fs-extra";
|
|
4
|
+
import type { PackageManager, ProjectContext } from "../types.ts";
|
|
5
|
+
|
|
6
|
+
export function detectPackageManager(): PackageManager {
|
|
7
|
+
if (fs.pathExistsSync("bun.lockb")) return "bun";
|
|
8
|
+
if (fs.pathExistsSync("pnpm-lock.yaml")) return "pnpm";
|
|
9
|
+
if (fs.pathExistsSync("yarn.lock")) return "yarn";
|
|
10
|
+
|
|
11
|
+
if (process.env.npm_execpath?.includes("bun")) {
|
|
12
|
+
return "bun";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
16
|
+
if (userAgent) {
|
|
17
|
+
if (userAgent.startsWith("bun")) return "bun";
|
|
18
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
19
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
20
|
+
if (userAgent.startsWith("npm")) return "npm";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return "npm";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getPackageManagerCommands(pm: PackageManager) {
|
|
27
|
+
const commands = {
|
|
28
|
+
bun: {
|
|
29
|
+
init: "bun init -y",
|
|
30
|
+
install: "bun install",
|
|
31
|
+
add: "bun add",
|
|
32
|
+
addDev: "bun add -D",
|
|
33
|
+
run: "bun run",
|
|
34
|
+
create: "bun create",
|
|
35
|
+
x: "bunx",
|
|
36
|
+
},
|
|
37
|
+
npm: {
|
|
38
|
+
init: "npm init -y",
|
|
39
|
+
install: "npm install",
|
|
40
|
+
add: "npm install",
|
|
41
|
+
addDev: "npm install --save-dev",
|
|
42
|
+
run: "npm run",
|
|
43
|
+
create: "npm create",
|
|
44
|
+
x: "npx",
|
|
45
|
+
},
|
|
46
|
+
pnpm: {
|
|
47
|
+
init: "pnpm init",
|
|
48
|
+
install: "pnpm install",
|
|
49
|
+
add: "pnpm add",
|
|
50
|
+
addDev: "pnpm add -D",
|
|
51
|
+
run: "pnpm run",
|
|
52
|
+
create: "pnpm create",
|
|
53
|
+
x: "pnpm dlx",
|
|
54
|
+
},
|
|
55
|
+
yarn: {
|
|
56
|
+
init: "yarn init -y",
|
|
57
|
+
install: "yarn install",
|
|
58
|
+
add: "yarn add",
|
|
59
|
+
addDev: "yarn add -D",
|
|
60
|
+
run: "yarn",
|
|
61
|
+
create: "yarn create",
|
|
62
|
+
x: "yarn dlx",
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return commands[pm];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function installDependencies(
|
|
70
|
+
context: ProjectContext,
|
|
71
|
+
{
|
|
72
|
+
dependencies,
|
|
73
|
+
devDependencies,
|
|
74
|
+
cwd,
|
|
75
|
+
}: {
|
|
76
|
+
dependencies?: string[];
|
|
77
|
+
devDependencies?: string[];
|
|
78
|
+
cwd?: string;
|
|
79
|
+
} = {},
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
const targetCwd = cwd || context.path;
|
|
82
|
+
const pm = context.packageManager;
|
|
83
|
+
const commands = getPackageManagerCommands(pm);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
if (!dependencies && !devDependencies) {
|
|
87
|
+
await execa(commands.install, {
|
|
88
|
+
cwd: targetCwd,
|
|
89
|
+
shell: true,
|
|
90
|
+
stdio: "pipe",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (dependencies) {
|
|
95
|
+
await execa(`${commands.add} ${dependencies.join(" ")}`, {
|
|
96
|
+
cwd: targetCwd,
|
|
97
|
+
shell: true,
|
|
98
|
+
stdio: "pipe",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (devDependencies) {
|
|
103
|
+
await execa(`${commands.addDev} ${devDependencies.join(" ")}`, {
|
|
104
|
+
cwd: targetCwd,
|
|
105
|
+
shell: true,
|
|
106
|
+
stdio: "pipe",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
log.error("Failed to install dependencies");
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|