@percepta/create 3.1.4 → 3.2.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/README.md +8 -8
- package/dist/git-ops-C2CIjuce.js +51 -0
- package/dist/git-ops-C2CIjuce.js.map +1 -0
- package/dist/index.js +1085 -1072
- package/dist/index.js.map +1 -0
- package/dist/init-CtCp7Tv2.js +52 -0
- package/dist/init-CtCp7Tv2.js.map +1 -0
- package/dist/status-CKe4aKso.js +48 -0
- package/dist/status-CKe4aKso.js.map +1 -0
- package/dist/sync-D1vkoofl.js +101 -0
- package/dist/sync-D1vkoofl.js.map +1 -0
- package/dist/upstream-D-LH_1z4.js +85 -0
- package/dist/upstream-D-LH_1z4.js.map +1 -0
- package/package.json +23 -24
- package/template-versions.json +1 -1
- package/templates/monorepo/.github/workflows/access-control.yml +38 -0
- package/templates/monorepo/README.md +41 -2
- package/templates/monorepo/access/README.md +39 -0
- package/templates/monorepo/access/bootstrap-grants.yaml.example +9 -0
- package/templates/monorepo/access/dev-grants.yaml.example +19 -0
- package/templates/monorepo/access/dev-groups.yaml.example +8 -0
- package/templates/monorepo/access/reconcile.yaml.example +11 -0
- package/templates/monorepo/auth/README.md +26 -0
- package/templates/monorepo/auth/drizzle.config.ts +13 -0
- package/templates/monorepo/auth/package.json +32 -0
- package/templates/monorepo/auth/scripts/setup-database.ts +57 -0
- package/templates/monorepo/auth/src/auth.ts +77 -0
- package/templates/monorepo/auth/src/config/database.ts +31 -0
- package/templates/monorepo/auth/src/drizzle/db.ts +9 -0
- package/templates/monorepo/auth/src/drizzle/migrations/0000_shared_auth.sql +89 -0
- package/templates/monorepo/auth/src/drizzle/migrations/meta/_journal.json +13 -0
- package/templates/{webapp → monorepo/auth}/src/drizzle/schema/auth/accounts.ts +1 -6
- package/templates/{webapp → monorepo/auth}/src/drizzle/schema/auth/sessions.ts +1 -5
- package/templates/{webapp → monorepo/auth}/src/drizzle/schema/auth/verifications.ts +0 -4
- package/templates/monorepo/auth/src/drizzle/schema/groups.ts +16 -0
- package/templates/monorepo/auth/src/drizzle/schema/index.ts +5 -0
- package/templates/monorepo/auth/src/drizzle/schema/users.ts +6 -0
- package/templates/monorepo/auth/src/index.ts +1 -0
- package/templates/monorepo/auth/src/scim/README.md +6 -0
- package/templates/monorepo/auth/tsconfig.json +12 -0
- package/templates/monorepo/package.json.template +18 -6
- package/templates/monorepo/pnpm-workspace.yaml +1 -0
- package/templates/webapp/AGENTS.md +13 -6
- package/templates/webapp/README.md +34 -18
- package/templates/webapp/agent-skills/access-control.md +301 -0
- package/templates/webapp/agent-skills/database.md +1 -1
- package/templates/webapp/docker-compose.yml +16 -0
- package/templates/webapp/env.example.template +9 -0
- package/templates/webapp/next.config.ts +1 -0
- package/templates/webapp/package.json.template +8 -4
- package/templates/webapp/scripts/seed.ts +87 -36
- package/templates/webapp/scripts/setup-database.ts +7 -1
- package/templates/webapp/scripts/start.sh +0 -9
- package/templates/webapp/src/access/access.manifest.ts +15 -0
- package/templates/webapp/src/access/schema.zed +7 -0
- package/templates/webapp/src/app/(app)/admin/_lib/PrincipalRoleTable.tsx +113 -0
- package/templates/webapp/src/app/(app)/admin/_lib/accessAdmin.ts +85 -0
- package/templates/webapp/src/app/(app)/admin/groups/page.tsx +117 -0
- package/templates/webapp/src/app/(app)/admin/users/page.tsx +79 -0
- package/templates/webapp/src/app/(app)/layout.tsx +16 -2
- package/templates/webapp/src/app/(app)/page.tsx +1 -12
- package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +2 -5
- package/templates/webapp/src/app/(auth)/auth/signup/page.tsx +2 -5
- package/templates/webapp/src/config/getEnvConfig.ts +8 -0
- package/templates/webapp/src/drizzle/db.ts +3 -4
- package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +1 -57
- package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +1 -347
- package/templates/webapp/src/drizzle/schema/index.ts +3 -4
- package/templates/webapp/src/lib/auth/index.ts +6 -81
- package/templates/webapp/src/server/api/root.ts +4 -1
- package/templates/webapp/src/server/api/routers/access.ts +13 -0
- package/templates/webapp/src/server/trpc.ts +42 -8
- package/templates/webapp/src/services/DatabaseService.ts +4 -5
- package/templates/webapp/src/services/access/AppAccessControl.ts +39 -0
- package/dist/chunk-CO3YWUD6.js +0 -139
- package/dist/chunk-DCM7JOSC.js +0 -49
- package/dist/chunk-V5EJIUBJ.js +0 -60
- package/dist/index.d.ts +0 -1
- package/dist/init-EQZ2TCSJ.js +0 -96
- package/dist/status-QW5TQDYY.js +0 -76
- package/dist/sync-RLBZDOFB.js +0 -136
- package/dist/upstream-TQFVPMEG.js +0 -144
- package/templates/webapp/scripts/create-user.ts +0 -47
- package/templates/webapp/src/drizzle/schema/auth/users.ts +0 -38
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import type { PermissionClient, SubjectRef } from "@percepta/access-control";
|
|
2
|
+
import {
|
|
3
|
+
createRequireApplicationAccess,
|
|
4
|
+
createRequirePermission,
|
|
5
|
+
} from "@percepta/access-control/trpc";
|
|
1
6
|
import { TRPCError, initTRPC } from "@trpc/server";
|
|
2
|
-
import { headers } from "next/headers";
|
|
3
7
|
import superjson from "superjson";
|
|
4
|
-
import {
|
|
8
|
+
import { accessManifest } from "../access/access.manifest";
|
|
9
|
+
import { type BetterAuthSession, getServerSession } from "../lib/auth";
|
|
5
10
|
import { AuthContextService } from "../services/AuthContextService";
|
|
11
|
+
import {
|
|
12
|
+
getAccessControl,
|
|
13
|
+
toUserSubject,
|
|
14
|
+
} from "../services/access/AppAccessControl";
|
|
6
15
|
import { getTracer } from "../services/logger/AppLogger";
|
|
7
16
|
|
|
8
17
|
export interface Context {
|
|
@@ -16,16 +25,23 @@ export interface ProtectedContext extends Context {
|
|
|
16
25
|
session: BetterAuthSession;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
28
|
+
const lazyPermissionClient = {
|
|
29
|
+
can(check) {
|
|
30
|
+
return getAccessControl().permissions.can(check);
|
|
31
|
+
},
|
|
32
|
+
} satisfies Pick<PermissionClient, "can">;
|
|
23
33
|
|
|
34
|
+
function getCurrentSubject(ctx: Context): SubjectRef | null {
|
|
35
|
+
const userId = ctx.session?.user.id;
|
|
36
|
+
return userId == null ? null : toUserSubject(userId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function createContext(): Promise<Context> {
|
|
24
40
|
const services: Context["services"] = {
|
|
25
41
|
authContext: AuthContextService.create(),
|
|
26
42
|
};
|
|
27
43
|
|
|
28
|
-
return { session, services };
|
|
44
|
+
return { session: await getServerSession(), services };
|
|
29
45
|
}
|
|
30
46
|
|
|
31
47
|
export const { router, procedure } = initTRPC.context<Context>().create({
|
|
@@ -42,7 +58,7 @@ export const { router, procedure } = initTRPC.context<Context>().create({
|
|
|
42
58
|
},
|
|
43
59
|
});
|
|
44
60
|
|
|
45
|
-
|
|
61
|
+
const requireAuthenticatedUser = procedure.use(
|
|
46
62
|
({ ctx, next }): ReturnType<typeof next<ProtectedContext>> => {
|
|
47
63
|
const {
|
|
48
64
|
session,
|
|
@@ -59,3 +75,21 @@ export const protectedProcedure = procedure.use(
|
|
|
59
75
|
);
|
|
60
76
|
},
|
|
61
77
|
);
|
|
78
|
+
|
|
79
|
+
const requireApplicationAccess =
|
|
80
|
+
createRequireApplicationAccess<ProtectedContext>({
|
|
81
|
+
accessControl: lazyPermissionClient,
|
|
82
|
+
getSubject: getCurrentSubject,
|
|
83
|
+
manifest: accessManifest,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const requirePermission = createRequirePermission<ProtectedContext>({
|
|
87
|
+
accessControl: lazyPermissionClient,
|
|
88
|
+
getSubject: getCurrentSubject,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const protectedProcedure = requireAuthenticatedUser;
|
|
92
|
+
|
|
93
|
+
export const appProcedure = requireAuthenticatedUser.use(
|
|
94
|
+
requireApplicationAccess,
|
|
95
|
+
);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { type NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
3
3
|
import { db } from "../drizzle/db";
|
|
4
|
-
import type * as schema from "../drizzle/schema";
|
|
5
4
|
|
|
6
5
|
export class DatabaseService {
|
|
7
6
|
private static SINGLETON: DatabaseService | undefined;
|
|
@@ -15,10 +14,10 @@ export class DatabaseService {
|
|
|
15
14
|
|
|
16
15
|
private transactionAsyncLocalStorage = new AsyncLocalStorage<LocalStorage>();
|
|
17
16
|
|
|
18
|
-
private constructor(private database: NodePgDatabase
|
|
17
|
+
private constructor(private database: NodePgDatabase) {}
|
|
19
18
|
|
|
20
19
|
public async createTransaction<TReturn>(
|
|
21
|
-
callback: (txn: NodePgDatabase
|
|
20
|
+
callback: (txn: NodePgDatabase) => Promise<TReturn>,
|
|
22
21
|
): Promise<TReturn> {
|
|
23
22
|
const currentContext = this.transactionAsyncLocalStorage.getStore();
|
|
24
23
|
if (currentContext != null) {
|
|
@@ -47,8 +46,8 @@ export class DatabaseService {
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
type Database = Omit<NodePgDatabase
|
|
49
|
+
type Database = Omit<NodePgDatabase, "transaction">;
|
|
51
50
|
|
|
52
51
|
interface LocalStorage {
|
|
53
|
-
txn: NodePgDatabase
|
|
52
|
+
txn: NodePgDatabase;
|
|
54
53
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AppAccessRuntime,
|
|
3
|
+
createAppAccessRuntime,
|
|
4
|
+
} from "@percepta/access-control";
|
|
5
|
+
import { accessManifest } from "../../access/access.manifest";
|
|
6
|
+
import { getEnvConfig } from "../../config/getEnvConfig";
|
|
7
|
+
|
|
8
|
+
export type AppAccessControl = AppAccessRuntime<typeof accessManifest>;
|
|
9
|
+
|
|
10
|
+
const appAccessRuntime = createAppAccessRuntime({
|
|
11
|
+
manifest: accessManifest,
|
|
12
|
+
spicedb: () => {
|
|
13
|
+
const {
|
|
14
|
+
NODE_ENV: nodeEnv,
|
|
15
|
+
SPICEDB_ENDPOINT: endpoint,
|
|
16
|
+
SPICEDB_INSECURE: insecure,
|
|
17
|
+
SPICEDB_PRESHARED_KEY: presharedKey,
|
|
18
|
+
} = getEnvConfig();
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
endpoint,
|
|
22
|
+
insecure,
|
|
23
|
+
nodeEnv,
|
|
24
|
+
presharedKey,
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export function canAccessApplication(userId: string): Promise<boolean> {
|
|
30
|
+
return appAccessRuntime.canAccessApplication(userId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getAccessControl(): AppAccessControl {
|
|
34
|
+
return appAccessRuntime.getAccessControl();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function toUserSubject(userId: string) {
|
|
38
|
+
return appAccessRuntime.toUserSubject(userId);
|
|
39
|
+
}
|
package/dist/chunk-CO3YWUD6.js
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
// src/utils/prompts.ts
|
|
2
|
-
import path from "path";
|
|
3
|
-
import inquirer from "inquirer";
|
|
4
|
-
|
|
5
|
-
// src/utils/validate.ts
|
|
6
|
-
import validateNpmPackageName from "validate-npm-package-name";
|
|
7
|
-
function validateProjectName(name) {
|
|
8
|
-
const result = validateNpmPackageName(name);
|
|
9
|
-
if (!result.validForNewPackages) {
|
|
10
|
-
const errors = [...result.errors || [], ...result.warnings || []];
|
|
11
|
-
return {
|
|
12
|
-
valid: false,
|
|
13
|
-
error: errors[0] || "Invalid package name"
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
return { valid: true };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// src/utils/case-converters.ts
|
|
20
|
-
function toKebabCase(str) {
|
|
21
|
-
return str.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
22
|
-
}
|
|
23
|
-
function toTitleCase(str) {
|
|
24
|
-
return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
25
|
-
}
|
|
26
|
-
function toSnakeCase(str) {
|
|
27
|
-
return str.replace(/-/g, "_");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// src/utils/prompts.ts
|
|
31
|
-
var VALID_PROJECT_TYPES = ["monorepo", "webapp", "library"];
|
|
32
|
-
function isValidProjectType(value) {
|
|
33
|
-
return typeof value === "string" && VALID_PROJECT_TYPES.includes(value);
|
|
34
|
-
}
|
|
35
|
-
async function promptName(message) {
|
|
36
|
-
const { name } = await inquirer.prompt([
|
|
37
|
-
{
|
|
38
|
-
type: "input",
|
|
39
|
-
name: "name",
|
|
40
|
-
message,
|
|
41
|
-
filter: toKebabCase,
|
|
42
|
-
validate: (input) => {
|
|
43
|
-
const result = validateProjectName(toKebabCase(input));
|
|
44
|
-
return result.valid || result.error || "Invalid project name";
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
]);
|
|
48
|
-
return name;
|
|
49
|
-
}
|
|
50
|
-
async function promptOutsideMonorepoType() {
|
|
51
|
-
const { webapp } = await inquirer.prompt([
|
|
52
|
-
{
|
|
53
|
-
type: "confirm",
|
|
54
|
-
name: "webapp",
|
|
55
|
-
message: "Initialize with a webapp?",
|
|
56
|
-
default: true
|
|
57
|
-
}
|
|
58
|
-
]);
|
|
59
|
-
return webapp ? "webapp" : "monorepo";
|
|
60
|
-
}
|
|
61
|
-
async function promptInsideMonorepoType() {
|
|
62
|
-
const { projectType } = await inquirer.prompt([
|
|
63
|
-
{
|
|
64
|
-
type: "rawlist",
|
|
65
|
-
name: "projectType",
|
|
66
|
-
message: "What kind of package?",
|
|
67
|
-
// inquirer v12 / @inquirer/rawlist v5 matches `default` against the
|
|
68
|
-
// choice's `value`, not its index. `default: 0` would be a no-op.
|
|
69
|
-
default: "webapp",
|
|
70
|
-
choices: [
|
|
71
|
-
{ name: "Webapp \u2014 A Next.js webapp", value: "webapp" },
|
|
72
|
-
{ name: "Library \u2014 A TypeScript library", value: "library" }
|
|
73
|
-
]
|
|
74
|
-
}
|
|
75
|
-
]);
|
|
76
|
-
return projectType;
|
|
77
|
-
}
|
|
78
|
-
async function promptProjectDetails(defaults) {
|
|
79
|
-
const inMonorepo = defaults.monorepoContext?.found ?? false;
|
|
80
|
-
const cwd = defaults.cwd ?? process.cwd();
|
|
81
|
-
let projectType;
|
|
82
|
-
let finalName;
|
|
83
|
-
if (inMonorepo) {
|
|
84
|
-
projectType = defaults.projectType ?? await promptInsideMonorepoType();
|
|
85
|
-
await defaults.beforeNamePrompt?.(projectType);
|
|
86
|
-
finalName = defaults.name || await promptName("Package name?");
|
|
87
|
-
} else {
|
|
88
|
-
const repoName = defaults.repoName || (defaults.projectType === "monorepo" ? defaults.name : void 0) || await promptName("Repo name?");
|
|
89
|
-
const repoTitle = toTitleCase(repoName);
|
|
90
|
-
projectType = defaults.projectType ?? await promptOutsideMonorepoType();
|
|
91
|
-
await defaults.beforeNamePrompt?.(projectType);
|
|
92
|
-
if (projectType === "monorepo") {
|
|
93
|
-
finalName = repoName;
|
|
94
|
-
const finalTitle3 = repoTitle;
|
|
95
|
-
const finalDirectory3 = path.resolve(cwd, repoName);
|
|
96
|
-
return {
|
|
97
|
-
projectType,
|
|
98
|
-
directory: finalDirectory3,
|
|
99
|
-
name: finalName,
|
|
100
|
-
title: finalTitle3,
|
|
101
|
-
installDeps: !defaults.skipInstall,
|
|
102
|
-
monorepoName: repoName,
|
|
103
|
-
monorepoTitle: repoTitle
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
const packageNamePrompt = projectType === "webapp" ? "Webapp name?" : "Library name?";
|
|
107
|
-
finalName = defaults.name || await promptName(packageNamePrompt);
|
|
108
|
-
const finalTitle2 = toTitleCase(finalName);
|
|
109
|
-
const finalDirectory2 = path.resolve(cwd, repoName);
|
|
110
|
-
return {
|
|
111
|
-
projectType,
|
|
112
|
-
directory: finalDirectory2,
|
|
113
|
-
name: finalName,
|
|
114
|
-
title: finalTitle2,
|
|
115
|
-
installDeps: !defaults.skipInstall,
|
|
116
|
-
monorepoName: repoName,
|
|
117
|
-
monorepoTitle: repoTitle
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
const finalTitle = finalName ? toTitleCase(finalName) : "";
|
|
121
|
-
const finalDirectory = !inMonorepo && finalName ? path.resolve(cwd, finalName) : "";
|
|
122
|
-
return {
|
|
123
|
-
projectType,
|
|
124
|
-
directory: finalDirectory,
|
|
125
|
-
name: finalName,
|
|
126
|
-
title: finalTitle,
|
|
127
|
-
installDeps: !defaults.skipInstall
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export {
|
|
132
|
-
validateProjectName,
|
|
133
|
-
toKebabCase,
|
|
134
|
-
toTitleCase,
|
|
135
|
-
toSnakeCase,
|
|
136
|
-
VALID_PROJECT_TYPES,
|
|
137
|
-
isValidProjectType,
|
|
138
|
-
promptProjectDetails
|
|
139
|
-
};
|
package/dist/chunk-DCM7JOSC.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// src/utils/git-ops.ts
|
|
2
|
-
import { execFileSync } from "child_process";
|
|
3
|
-
function toGitPath(p) {
|
|
4
|
-
return p.replace(/\\/g, "/");
|
|
5
|
-
}
|
|
6
|
-
function getLatestTemplateTag(type, repoPath) {
|
|
7
|
-
try {
|
|
8
|
-
const tags = execFileSync(
|
|
9
|
-
"git",
|
|
10
|
-
["tag", "-l", `template/${type}/*`, "--sort=-v:refname"],
|
|
11
|
-
{ cwd: repoPath, encoding: "utf-8" }
|
|
12
|
-
).trim();
|
|
13
|
-
if (!tags) return null;
|
|
14
|
-
return tags.split("\n")[0] ?? null;
|
|
15
|
-
} catch {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function getTemplateVersionFromTag(tag) {
|
|
20
|
-
const parts = tag.split("/");
|
|
21
|
-
return parts[parts.length - 1] ?? "";
|
|
22
|
-
}
|
|
23
|
-
function getTemplateDiff(repoPath, templatePath, fromTag, toTag) {
|
|
24
|
-
return execFileSync(
|
|
25
|
-
"git",
|
|
26
|
-
["diff", `${fromTag}..${toTag}`, "--", toGitPath(templatePath)],
|
|
27
|
-
{
|
|
28
|
-
cwd: repoPath,
|
|
29
|
-
encoding: "utf-8"
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
function getFileAtTag(repoPath, tag, filePath) {
|
|
34
|
-
try {
|
|
35
|
-
return execFileSync("git", ["show", `${tag}:${toGitPath(filePath)}`], {
|
|
36
|
-
cwd: repoPath,
|
|
37
|
-
encoding: "utf-8"
|
|
38
|
-
});
|
|
39
|
-
} catch {
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export {
|
|
45
|
-
getLatestTemplateTag,
|
|
46
|
-
getTemplateVersionFromTag,
|
|
47
|
-
getTemplateDiff,
|
|
48
|
-
getFileAtTag
|
|
49
|
-
};
|
package/dist/chunk-V5EJIUBJ.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// src/utils/manifest.ts
|
|
2
|
-
import path from "path";
|
|
3
|
-
import fs from "fs-extra";
|
|
4
|
-
var MANIFEST_FILENAME = ".mosaic-template.json";
|
|
5
|
-
function getManifestPath(dir) {
|
|
6
|
-
return path.join(dir, MANIFEST_FILENAME);
|
|
7
|
-
}
|
|
8
|
-
async function readManifest(dir) {
|
|
9
|
-
const manifestPath = getManifestPath(dir);
|
|
10
|
-
if (!await fs.pathExists(manifestPath)) {
|
|
11
|
-
throw new Error(
|
|
12
|
-
`No ${MANIFEST_FILENAME} found in ${dir}. Run 'create init' to create one.`
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
const content = await fs.readFile(manifestPath, "utf-8");
|
|
16
|
-
try {
|
|
17
|
-
return JSON.parse(content);
|
|
18
|
-
} catch (error) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
`Invalid JSON in ${MANIFEST_FILENAME}: ${error.message}`
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
async function writeManifest(dir, manifest) {
|
|
25
|
-
const manifestPath = getManifestPath(dir);
|
|
26
|
-
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
27
|
-
}
|
|
28
|
-
async function manifestExists(dir) {
|
|
29
|
-
return fs.pathExists(getManifestPath(dir));
|
|
30
|
-
}
|
|
31
|
-
function derivePlaceholders(appName, appTitle, repoName = appName) {
|
|
32
|
-
const nameSnake = appName.replace(/-/g, "_");
|
|
33
|
-
const repoNameSnake = repoName.replace(/-/g, "_");
|
|
34
|
-
return {
|
|
35
|
-
__APP_NAME__: appName,
|
|
36
|
-
__APP_TITLE__: appTitle,
|
|
37
|
-
__DB_NAME__: nameSnake + "_db",
|
|
38
|
-
__APP_NAME_UPPER__: appName.toUpperCase(),
|
|
39
|
-
__APP_NAME_SNAKE__: nameSnake,
|
|
40
|
-
__REPO_NAME__: repoName,
|
|
41
|
-
__REPO_NAME_SNAKE__: repoNameSnake
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function resolveMosaicTemplatePath(options) {
|
|
45
|
-
if (options.mosaicTemplatePath)
|
|
46
|
-
return path.resolve(options.mosaicTemplatePath);
|
|
47
|
-
if (process.env.MOSAIC_TEMPLATE_PATH)
|
|
48
|
-
return path.resolve(process.env.MOSAIC_TEMPLATE_PATH);
|
|
49
|
-
throw new Error(
|
|
50
|
-
"Mosaic repo path required. Use --mosaic-template-path or set MOSAIC_TEMPLATE_PATH."
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export {
|
|
55
|
-
readManifest,
|
|
56
|
-
writeManifest,
|
|
57
|
-
manifestExists,
|
|
58
|
-
derivePlaceholders,
|
|
59
|
-
resolveMosaicTemplatePath
|
|
60
|
-
};
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
package/dist/init-EQZ2TCSJ.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
VALID_PROJECT_TYPES,
|
|
3
|
-
isValidProjectType
|
|
4
|
-
} from "./chunk-CO3YWUD6.js";
|
|
5
|
-
import {
|
|
6
|
-
derivePlaceholders,
|
|
7
|
-
manifestExists,
|
|
8
|
-
writeManifest
|
|
9
|
-
} from "./chunk-V5EJIUBJ.js";
|
|
10
|
-
|
|
11
|
-
// src/commands/init.ts
|
|
12
|
-
import path from "path";
|
|
13
|
-
import fs from "fs-extra";
|
|
14
|
-
import chalk from "chalk";
|
|
15
|
-
import inquirer from "inquirer";
|
|
16
|
-
async function initCommand(options) {
|
|
17
|
-
const cwd = process.cwd();
|
|
18
|
-
if (await manifestExists(cwd)) {
|
|
19
|
-
console.error(
|
|
20
|
-
chalk.red(".mosaic-template.json already exists in this directory.")
|
|
21
|
-
);
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
25
|
-
let appName = path.basename(cwd);
|
|
26
|
-
if (await fs.pathExists(pkgPath)) {
|
|
27
|
-
const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
|
|
28
|
-
appName = pkg.name?.replace(/^@[^/]+\//, "") || appName;
|
|
29
|
-
}
|
|
30
|
-
let templateType = options.type;
|
|
31
|
-
if (templateType && !isValidProjectType(templateType)) {
|
|
32
|
-
console.error(
|
|
33
|
-
chalk.red(
|
|
34
|
-
`Invalid template type "${templateType}". Valid types: ${VALID_PROJECT_TYPES.join(", ")}`
|
|
35
|
-
)
|
|
36
|
-
);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
if (!templateType) {
|
|
40
|
-
const answer = await inquirer.prompt([
|
|
41
|
-
{
|
|
42
|
-
type: "list",
|
|
43
|
-
name: "type",
|
|
44
|
-
message: "Template type:",
|
|
45
|
-
choices: ["webapp", "library"]
|
|
46
|
-
}
|
|
47
|
-
]);
|
|
48
|
-
templateType = answer.type;
|
|
49
|
-
}
|
|
50
|
-
const templateVersion = options.templateVersion || "1.0.0";
|
|
51
|
-
const appTitle = appName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
52
|
-
const manifest = {
|
|
53
|
-
templateType,
|
|
54
|
-
templateVersion,
|
|
55
|
-
templateCommit: "unknown",
|
|
56
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
57
|
-
placeholders: derivePlaceholders(appName, appTitle),
|
|
58
|
-
source: {
|
|
59
|
-
templatePath: `packages/create-mosaic-module/templates/${templateType}`
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
await writeManifest(cwd, manifest);
|
|
63
|
-
const notesPath = path.join(cwd, "mosaic-template-notes.md");
|
|
64
|
-
if (!await fs.pathExists(notesPath)) {
|
|
65
|
-
await fs.writeFile(
|
|
66
|
-
notesPath,
|
|
67
|
-
`# Mosaic Divergence Notes
|
|
68
|
-
|
|
69
|
-
Document intentional differences from the ${templateType} template here.
|
|
70
|
-
Claude reads this file during sync to preserve your customizations.
|
|
71
|
-
|
|
72
|
-
## Intentional Divergences
|
|
73
|
-
|
|
74
|
-
`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
console.log();
|
|
78
|
-
console.log(
|
|
79
|
-
chalk.green("\u2714"),
|
|
80
|
-
chalk.bold("Initialized .mosaic-template.json")
|
|
81
|
-
);
|
|
82
|
-
console.log();
|
|
83
|
-
console.log(chalk.dim(" Template:"), templateType);
|
|
84
|
-
console.log(chalk.dim(" Version:"), templateVersion);
|
|
85
|
-
console.log(chalk.dim(" App name:"), appName);
|
|
86
|
-
console.log();
|
|
87
|
-
console.log(
|
|
88
|
-
chalk.dim(
|
|
89
|
-
"Review .mosaic-template.json and mosaic-template-notes.md, then commit them."
|
|
90
|
-
)
|
|
91
|
-
);
|
|
92
|
-
console.log();
|
|
93
|
-
}
|
|
94
|
-
export {
|
|
95
|
-
initCommand
|
|
96
|
-
};
|
package/dist/status-QW5TQDYY.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getLatestTemplateTag,
|
|
3
|
-
getTemplateVersionFromTag
|
|
4
|
-
} from "./chunk-DCM7JOSC.js";
|
|
5
|
-
import {
|
|
6
|
-
readManifest
|
|
7
|
-
} from "./chunk-V5EJIUBJ.js";
|
|
8
|
-
|
|
9
|
-
// src/commands/status.ts
|
|
10
|
-
import path from "path";
|
|
11
|
-
import chalk from "chalk";
|
|
12
|
-
async function statusCommand(options) {
|
|
13
|
-
const cwd = process.cwd();
|
|
14
|
-
try {
|
|
15
|
-
const manifest = await readManifest(cwd);
|
|
16
|
-
console.log();
|
|
17
|
-
console.log(chalk.bold("Mosaic Template Status"));
|
|
18
|
-
console.log();
|
|
19
|
-
console.log(chalk.dim(" Template type:"), manifest.templateType);
|
|
20
|
-
console.log(chalk.dim(" Current version:"), manifest.templateVersion);
|
|
21
|
-
console.log(chalk.dim(" Template commit:"), manifest.templateCommit);
|
|
22
|
-
console.log(chalk.dim(" Created:"), manifest.createdAt);
|
|
23
|
-
if (manifest.lastSyncedAt) {
|
|
24
|
-
console.log(chalk.dim(" Last synced:"), manifest.lastSyncedAt);
|
|
25
|
-
}
|
|
26
|
-
const rawPath = options.mosaicTemplatePath || process.env.MOSAIC_TEMPLATE_PATH;
|
|
27
|
-
const mosaicTemplatePath = rawPath ? path.resolve(rawPath) : void 0;
|
|
28
|
-
if (mosaicTemplatePath) {
|
|
29
|
-
const latestTag = getLatestTemplateTag(
|
|
30
|
-
manifest.templateType,
|
|
31
|
-
mosaicTemplatePath
|
|
32
|
-
);
|
|
33
|
-
if (latestTag) {
|
|
34
|
-
const latestVersion = getTemplateVersionFromTag(latestTag);
|
|
35
|
-
console.log(chalk.dim(" Latest version:"), latestVersion);
|
|
36
|
-
console.log();
|
|
37
|
-
if (latestVersion !== manifest.templateVersion) {
|
|
38
|
-
console.log(
|
|
39
|
-
chalk.yellow(
|
|
40
|
-
` Update available: ${manifest.templateVersion} \u2192 ${latestVersion}`
|
|
41
|
-
)
|
|
42
|
-
);
|
|
43
|
-
console.log(
|
|
44
|
-
chalk.dim(" Run:"),
|
|
45
|
-
`create sync --mosaic-template-path ${mosaicTemplatePath}`
|
|
46
|
-
);
|
|
47
|
-
} else {
|
|
48
|
-
console.log(chalk.green(" Up to date"));
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
console.log();
|
|
52
|
-
console.log(
|
|
53
|
-
chalk.yellow(" No template tags found in mosaic repo.")
|
|
54
|
-
);
|
|
55
|
-
console.log(
|
|
56
|
-
chalk.dim(" Run:"),
|
|
57
|
-
`cd ${mosaicTemplatePath} && pnpm template:tag`
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
} else {
|
|
61
|
-
console.log();
|
|
62
|
-
console.log(
|
|
63
|
-
chalk.dim(
|
|
64
|
-
" Use --mosaic-template-path or set MOSAIC_TEMPLATE_PATH to check for updates"
|
|
65
|
-
)
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
console.log();
|
|
69
|
-
} catch (error) {
|
|
70
|
-
console.error(chalk.red(error.message));
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
export {
|
|
75
|
-
statusCommand
|
|
76
|
-
};
|