@sanity/cli 7.15.0 → 7.16.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 +88 -1
- package/dist/actions/build/shouldAutoUpdate.js +28 -5
- package/dist/actions/build/shouldAutoUpdate.js.map +1 -1
- package/dist/actions/deploy/deployApp.js +4 -8
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployChecks.js +49 -31
- package/dist/actions/deploy/deployChecks.js.map +1 -1
- package/dist/actions/deploy/deployRunner.js +1 -1
- package/dist/actions/deploy/deployRunner.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +12 -10
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/deploymentPlan.js +2 -2
- package/dist/actions/deploy/deploymentPlan.js.map +1 -1
- package/dist/actions/deploy/resolveDeployTarget.js +13 -13
- package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
- package/dist/actions/init/bootstrapLocalTemplate.js +2 -3
- package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -1
- package/dist/actions/init/createAppCliConfig.js.map +1 -1
- package/dist/actions/init/createCliConfig.js.map +1 -1
- package/dist/actions/init/initNextJs.js +1 -1
- package/dist/actions/init/initNextJs.js.map +1 -1
- package/dist/actions/init/initStudio.js +10 -6
- package/dist/actions/init/initStudio.js.map +1 -1
- package/dist/actions/init/templates/appSanityUi.js +1 -1
- package/dist/actions/init/templates/appSanityUi.js.map +1 -1
- package/dist/actions/init/templates/getStarted.js +2 -2
- package/dist/actions/init/templates/getStarted.js.map +1 -1
- package/dist/actions/init/templates/nextjs/schemaTypes/blog.js +4 -4
- package/dist/actions/init/templates/nextjs/schemaTypes/blog.js.map +1 -1
- package/dist/actions/init/templates/shopify.js +2 -2
- package/dist/actions/init/templates/shopify.js.map +1 -1
- package/dist/actions/init/templates/shopifyOnline.js +2 -2
- package/dist/actions/init/templates/shopifyOnline.js.map +1 -1
- package/dist/actions/manifest/extractCoreAppManifest.js +7 -7
- package/dist/actions/manifest/extractCoreAppManifest.js.map +1 -1
- package/dist/actions/scaffold/createFrontend.js +107 -0
- package/dist/actions/scaffold/createFrontend.js.map +1 -0
- package/dist/actions/scaffold/scaffoldProject.js +202 -0
- package/dist/actions/scaffold/scaffoldProject.js.map +1 -0
- package/dist/actions/telemetry/resolveConsent.js +4 -2
- package/dist/actions/telemetry/resolveConsent.js.map +1 -1
- package/dist/commands/backups/list.js +1 -1
- package/dist/commands/backups/list.js.map +1 -1
- package/dist/commands/datasets/copy.js +5 -3
- package/dist/commands/datasets/copy.js.map +1 -1
- package/dist/commands/documents/query.js +5 -4
- package/dist/commands/documents/query.js.map +1 -1
- package/dist/commands/migrations/list.js +1 -1
- package/dist/commands/migrations/list.js.map +1 -1
- package/dist/commands/migrations/run.js +1 -1
- package/dist/commands/migrations/run.js.map +1 -1
- package/dist/commands/new.js +309 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/organizations/list.js +1 -1
- package/dist/commands/organizations/list.js.map +1 -1
- package/dist/commands/projects/unclaimed.js +205 -0
- package/dist/commands/projects/unclaimed.js.map +1 -0
- package/dist/commands/tokens/list.js +1 -1
- package/dist/commands/tokens/list.js.map +1 -1
- package/dist/commands/users/list.js +5 -1
- package/dist/commands/users/list.js.map +1 -1
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +4 -0
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
- package/dist/exports/invokeSanityCli/index.d.ts +5 -9
- package/dist/exports/invokeSanityCli/index.js +47 -20
- package/dist/exports/invokeSanityCli/index.js.map +1 -1
- package/dist/exports/runtime.d.ts +12 -0
- package/dist/exports/runtime.js.map +1 -1
- package/dist/services/mintProject.js +91 -0
- package/dist/services/mintProject.js.map +1 -0
- package/dist/services/projects.js +19 -0
- package/dist/services/projects.js.map +1 -1
- package/dist/services/telemetry.js +4 -0
- package/dist/services/telemetry.js.map +1 -1
- package/dist/util/formatClaimDeadline.js +15 -0
- package/dist/util/formatClaimDeadline.js.map +1 -0
- package/dist/util/mintProjectConstants.js +6 -0
- package/dist/util/mintProjectConstants.js.map +1 -0
- package/dist/util/newCommandSplash.js +27 -0
- package/dist/util/newCommandSplash.js.map +1 -0
- package/dist/util/packageManager/installPackages.js +57 -28
- package/dist/util/packageManager/installPackages.js.map +1 -1
- package/dist/util/unclaimedProjects.js +105 -0
- package/dist/util/unclaimedProjects.js.map +1 -0
- package/oclif.manifest.json +150 -41
- package/package.json +7 -7
- package/templates/get-started/plugins/sanity-plugin-tutorial/GetStartedTutorial.tsx +1 -1
- package/templates/shopify/components/inputs/CollectionHidden.tsx +1 -1
- package/templates/shopify/components/inputs/ProductHidden.tsx +1 -1
- package/templates/shopify/components/inputs/ProductVariantHidden.tsx +1 -1
- package/templates/shopify/components/inputs/ProxyString.tsx +1 -1
- package/templates/shopify/components/media/ShopifyDocumentStatus.tsx +3 -1
- package/templates/shopify/constants.ts +3 -1
- package/templates/shopify/plugins/customDocumentActions/shopifyDelete.tsx +1 -1
- package/templates/shopify/plugins/customDocumentActions/shopifyLink.ts +1 -1
- package/templates/shopify/schemaTypes/documents/collection.tsx +1 -1
- package/templates/shopify/schemaTypes/documents/colorTheme.tsx +1 -1
- package/templates/shopify/schemaTypes/documents/page.ts +1 -1
- package/templates/shopify/schemaTypes/documents/product.tsx +1 -1
- package/templates/shopify/schemaTypes/documents/productVariant.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/collection/collectionGroupType.ts +1 -1
- package/templates/shopify/schemaTypes/objects/hotspot/imageWithProductHotspotsType.ts +1 -1
- package/templates/shopify/schemaTypes/objects/link/linkEmailType.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/link/linkExternalType.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/link/linkInternalType.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/link/linkProductType.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/module/accordionType.ts +1 -1
- package/templates/shopify/schemaTypes/objects/module/callToActionType.tsx +2 -1
- package/templates/shopify/schemaTypes/objects/module/calloutType.ts +1 -1
- package/templates/shopify/schemaTypes/objects/module/collectionReferenceType.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/module/gridType.ts +1 -1
- package/templates/shopify/schemaTypes/objects/module/imageFeatureType.ts +1 -1
- package/templates/shopify/schemaTypes/objects/module/imageFeaturesType.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/module/instagramType.ts +1 -1
- package/templates/shopify/schemaTypes/objects/module/productFeaturesType.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/module/productReferenceType.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/shopify/collectionRuleType.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/shopify/optionType.tsx +1 -1
- package/templates/shopify/schemaTypes/objects/shopify/productWithVariantType.tsx +1 -1
- package/templates/shopify/schemaTypes/singletons/homeType.ts +1 -1
- package/templates/shopify/schemaTypes/singletons/settingsType.ts +5 -1
- package/templates/shopify/structure/pageStructure.ts +1 -1
- package/templates/shopify/structure/productStructure.ts +1 -1
- package/templates/shopify-online-storefront/components/inputs/CollectionHidden.tsx +1 -1
- package/templates/shopify-online-storefront/components/inputs/ProductHidden.tsx +1 -1
- package/templates/shopify-online-storefront/components/inputs/ProductVariantHidden.tsx +1 -1
- package/templates/shopify-online-storefront/components/inputs/ProxyString.tsx +1 -1
- package/templates/shopify-online-storefront/components/media/ShopifyDocumentStatus.tsx +3 -1
- package/templates/shopify-online-storefront/plugins/shopifyDocumentActions/shopifyDelete.tsx +1 -1
- package/templates/shopify-online-storefront/plugins/shopifyDocumentActions/shopifyLink.ts +1 -1
- package/templates/shopify-online-storefront/schemaTypes/documents/collection.tsx +1 -1
- package/templates/shopify-online-storefront/schemaTypes/documents/product.tsx +1 -1
- package/templates/shopify-online-storefront/schemaTypes/documents/productVariant.tsx +1 -1
- package/templates/shopify-online-storefront/schemaTypes/objects/accordion.ts +1 -1
- package/templates/shopify-online-storefront/schemaTypes/objects/callout.ts +1 -1
- package/templates/shopify-online-storefront/schemaTypes/objects/option.ts +1 -1
- package/templates/shopify-online-storefront/schemaTypes/objects/shopifyCollectionRule.tsx +1 -1
- package/templates/shopify-online-storefront/structure/productStructure.ts +1 -1
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { CLIError } from '@oclif/core/errors';
|
|
4
|
+
import { exitCodes } from '@sanity/cli-core';
|
|
5
|
+
import { SanityCommand } from '@sanity/cli-core/SanityCommand';
|
|
6
|
+
import { createFlow, input } from '@sanity/cli-core/ux';
|
|
7
|
+
import { FRONTEND_DIR, FRONTEND_ENV_FILE, getUnavailableScaffoldTarget, scaffoldProject, STUDIO_DIR, STUDIO_ENV_FILE } from '../actions/scaffold/scaffoldProject.js';
|
|
8
|
+
import { mintUnclaimedProject } from '../services/mintProject.js';
|
|
9
|
+
import { formatClaimDeadline } from '../util/formatClaimDeadline.js';
|
|
10
|
+
import { CLAIM_WINDOW_HOURS, SANITY_NEW_URL } from '../util/mintProjectConstants.js';
|
|
11
|
+
import { renderNewCommandSplash } from '../util/newCommandSplash.js';
|
|
12
|
+
import { recordUnclaimedProject } from '../util/unclaimedProjects.js';
|
|
13
|
+
const DEFAULT_PROJECT_NAME = 'My Sanity project';
|
|
14
|
+
const HELP_DESCRIPTION_WIDTH = 78;
|
|
15
|
+
function formatHelpDescription(...paragraphs) {
|
|
16
|
+
return paragraphs.map((paragraph)=>{
|
|
17
|
+
const lines = [];
|
|
18
|
+
let line = '';
|
|
19
|
+
for (const word of paragraph.split(/\s+/u)){
|
|
20
|
+
if (!line || line.length + word.length + 1 <= HELP_DESCRIPTION_WIDTH) {
|
|
21
|
+
line = line ? `${line} ${word}` : word;
|
|
22
|
+
} else {
|
|
23
|
+
lines.push(line);
|
|
24
|
+
line = word;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (line) lines.push(line);
|
|
28
|
+
return lines.join('\n');
|
|
29
|
+
}).join('\n\n');
|
|
30
|
+
}
|
|
31
|
+
const FRONTEND_DEV_COMMANDS = {
|
|
32
|
+
bun: 'bun dev',
|
|
33
|
+
manual: 'npm run dev',
|
|
34
|
+
npm: 'npm run dev',
|
|
35
|
+
pnpm: 'pnpm dev',
|
|
36
|
+
yarn: 'yarn dev'
|
|
37
|
+
};
|
|
38
|
+
function frontendDevCommand(packageManager) {
|
|
39
|
+
return packageManager ? FRONTEND_DEV_COMMANDS[packageManager] : 'npm run dev';
|
|
40
|
+
}
|
|
41
|
+
function frontendInstallCommand(packageManager) {
|
|
42
|
+
switch(packageManager){
|
|
43
|
+
case 'bun':
|
|
44
|
+
{
|
|
45
|
+
return 'bun add next-sanity';
|
|
46
|
+
}
|
|
47
|
+
case 'pnpm':
|
|
48
|
+
{
|
|
49
|
+
return 'pnpm add --save-prod next-sanity';
|
|
50
|
+
}
|
|
51
|
+
case 'yarn':
|
|
52
|
+
{
|
|
53
|
+
return 'yarn add next-sanity';
|
|
54
|
+
}
|
|
55
|
+
default:
|
|
56
|
+
{
|
|
57
|
+
return 'npm install next-sanity';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function toResult(project) {
|
|
62
|
+
return {
|
|
63
|
+
apiHost: project.apiHost,
|
|
64
|
+
claimApiUrl: project.claimApiUrl,
|
|
65
|
+
claimToken: project.claimToken,
|
|
66
|
+
claimUrl: project.claimUrl,
|
|
67
|
+
dataset: project.datasetName,
|
|
68
|
+
expiresAt: project.expiresAt,
|
|
69
|
+
projectId: project.resourceId,
|
|
70
|
+
terms: {
|
|
71
|
+
notice: project.termsNotice,
|
|
72
|
+
url: project.termsUrl
|
|
73
|
+
},
|
|
74
|
+
token: project.token
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export class NewCommand extends SanityCommand {
|
|
78
|
+
static args = {
|
|
79
|
+
projectName: Args.string({
|
|
80
|
+
description: 'Display name for the new project',
|
|
81
|
+
required: false
|
|
82
|
+
})
|
|
83
|
+
};
|
|
84
|
+
static description = formatHelpDescription(`Sets up two folders here: ./${STUDIO_DIR}, a Studio where you write and edit your content, ` + `and ./${FRONTEND_DIR}, a Next.js website that reads it. Both are already connected to your ` + 'new project, so you can start them straight away. Use --no-scaffold if you just want the ' + 'project and nothing else.', `The project is real and works immediately, but it is only yours for ${CLAIM_WINDOW_HOURS} hours. Claim it with ` + 'a Sanity account before the deadline and everything you have built stays exactly as it is. ' + 'Claiming is free and takes about a minute. Miss the deadline and the project and its ' + 'content are deleted.', 'Two things to keep private: the claim link, because anyone who opens it becomes the owner, ' + `and the access token saved in ./${STUDIO_ENV_FILE} and ./${FRONTEND_ENV_FILE}, because it ` + 'can read and change everything in the project. Keep both env files out of git, and never ' + 'put the token in code that runs in the browser.', `Fetch ${SANITY_NEW_URL} for full instructions, or point your AI agent at it.`);
|
|
85
|
+
static enableJsonFlag = true;
|
|
86
|
+
static examples = [
|
|
87
|
+
{
|
|
88
|
+
command: '<%= config.bin %> <%= command.id %>',
|
|
89
|
+
description: 'Create a project with a Studio and a website'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
command: '<%= config.bin %> <%= command.id %> "My New Project"',
|
|
93
|
+
description: 'Create a project called "My New Project"'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
command: '<%= config.bin %> <%= command.id %> --yes',
|
|
97
|
+
description: 'Create a project without being asked anything'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
command: '<%= config.bin %> <%= command.id %> --no-scaffold',
|
|
101
|
+
description: 'Create the project only, with no Studio or website'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
command: '<%= config.bin %> <%= command.id %> --json',
|
|
105
|
+
description: 'Create a project and print its details as JSON'
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
static flags = {
|
|
109
|
+
scaffold: Flags.boolean({
|
|
110
|
+
allowNo: true,
|
|
111
|
+
default: true,
|
|
112
|
+
description: `Set up a Studio in ./${STUDIO_DIR} and a Next.js website in ./${FRONTEND_DIR} (on by default)`
|
|
113
|
+
}),
|
|
114
|
+
yes: Flags.boolean({
|
|
115
|
+
char: 'y',
|
|
116
|
+
default: false,
|
|
117
|
+
description: `Skip prompts and use defaults (project: "${DEFAULT_PROJECT_NAME}")`
|
|
118
|
+
})
|
|
119
|
+
};
|
|
120
|
+
static summary = `Create a Sanity project without an account, and claim it within ${CLAIM_WINDOW_HOURS} hours to keep it.`;
|
|
121
|
+
async run() {
|
|
122
|
+
const { output } = this;
|
|
123
|
+
const json = this.jsonEnabled();
|
|
124
|
+
const flow = createFlow(output.log);
|
|
125
|
+
const invoked = [
|
|
126
|
+
this.config.bin,
|
|
127
|
+
...this.id?.split(':') ?? []
|
|
128
|
+
].join(' ');
|
|
129
|
+
const cwd = process.cwd();
|
|
130
|
+
const unavailableScaffoldTarget = !json && this.flags.scaffold ? await getUnavailableScaffoldTarget(cwd) : undefined;
|
|
131
|
+
if (unavailableScaffoldTarget) {
|
|
132
|
+
throw new CLIError(`./${unavailableScaffoldTarget} is not an empty directory. No project was created.`, {
|
|
133
|
+
code: unavailableScaffoldTarget === STUDIO_DIR ? 'EXISTING_STUDIO_DIRECTORY' : 'EXISTING_FRONTEND_DIRECTORY',
|
|
134
|
+
exit: exitCodes.RUNTIME_ERROR,
|
|
135
|
+
suggestions: [
|
|
136
|
+
`Run this command where ./${unavailableScaffoldTarget} does not exist or is an empty directory`,
|
|
137
|
+
`Or run \`${invoked} --no-scaffold\` to create the project without changing ./${unavailableScaffoldTarget}`
|
|
138
|
+
]
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (!json) {
|
|
142
|
+
renderNewCommandSplash(output.log);
|
|
143
|
+
flow.intro('Setting up your Sanity project.');
|
|
144
|
+
flow.gap();
|
|
145
|
+
if (!this.isUnattended()) {
|
|
146
|
+
flow.note(`${styleText('cyan', `${invoked} --yes`)} for a non-interactive flow with defaults.`);
|
|
147
|
+
flow.gap();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
let displayName = this.args.projectName?.trim();
|
|
151
|
+
if (!displayName && this.isUnattended()) {
|
|
152
|
+
displayName = DEFAULT_PROJECT_NAME;
|
|
153
|
+
} else if (!displayName) {
|
|
154
|
+
displayName = (await input({
|
|
155
|
+
default: DEFAULT_PROJECT_NAME,
|
|
156
|
+
message: 'Project name'
|
|
157
|
+
})).trim() || DEFAULT_PROJECT_NAME;
|
|
158
|
+
}
|
|
159
|
+
const spin = json ? undefined : flow.spin('Creating your project...');
|
|
160
|
+
let project;
|
|
161
|
+
try {
|
|
162
|
+
project = await mintUnclaimedProject({
|
|
163
|
+
displayName
|
|
164
|
+
});
|
|
165
|
+
} catch (err) {
|
|
166
|
+
spin?.fail('Creating your project failed.');
|
|
167
|
+
throw err;
|
|
168
|
+
}
|
|
169
|
+
const recorded = recordUnclaimedProject(project);
|
|
170
|
+
spin?.succeed(`Created "${displayName}"`);
|
|
171
|
+
if (!json) {
|
|
172
|
+
flow.result(`Project ID: ${styleText('cyan', project.resourceId)}`);
|
|
173
|
+
flow.result(`Dataset: ${styleText('cyan', project.datasetName)} (where your content lives)`);
|
|
174
|
+
flow.result(`Token: ${styleText('cyan', project.token)}`);
|
|
175
|
+
flow.link(project.claimUrl, {
|
|
176
|
+
label: 'Claim link:'
|
|
177
|
+
});
|
|
178
|
+
flow.gap();
|
|
179
|
+
flow.highlight(`Claim your project by ${styleText('yellow', formatClaimDeadline(project.expiresAt))} to avoid losing it.`);
|
|
180
|
+
flow.gap();
|
|
181
|
+
flow.line('To use the Sanity CLI before claiming use your token by setting');
|
|
182
|
+
flow.command(`SANITY_AUTH_TOKEN="${project.token}" sanity "command"`);
|
|
183
|
+
flow.gap();
|
|
184
|
+
flow.note(styleText('dim', 'If you are an agent: give this claim URL to the person you are working for. They have ' + 'to open it themselves before the deadline.'));
|
|
185
|
+
flow.gap();
|
|
186
|
+
}
|
|
187
|
+
const printStudioInstructions = ()=>{
|
|
188
|
+
flow.highlight('Start your Studio:');
|
|
189
|
+
flow.gap();
|
|
190
|
+
flow.command(`cd ${STUDIO_DIR} && npx sanity dev`);
|
|
191
|
+
flow.gap();
|
|
192
|
+
flow.link(`http://localhost:3333/#token=${encodeURIComponent(project.token)}`, {
|
|
193
|
+
label: 'Then open this link:'
|
|
194
|
+
});
|
|
195
|
+
flow.gap();
|
|
196
|
+
flow.line('The token signs you in: there is no account yet.');
|
|
197
|
+
};
|
|
198
|
+
const printWebsiteInstructions = (packageManager)=>{
|
|
199
|
+
flow.highlight('In a separate terminal, start your website:');
|
|
200
|
+
flow.gap();
|
|
201
|
+
flow.command(`cd ${FRONTEND_DIR} && ${frontendDevCommand(packageManager)}`);
|
|
202
|
+
flow.gap();
|
|
203
|
+
flow.link('http://localhost:3000', {
|
|
204
|
+
label: 'Then open this link:'
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
let scaffold;
|
|
208
|
+
if (!json && this.flags.scaffold) {
|
|
209
|
+
const scaffoldController = new AbortController();
|
|
210
|
+
const abortScaffold = ()=>scaffoldController.abort(new Error('SIGINT'));
|
|
211
|
+
process.once('SIGINT', abortScaffold);
|
|
212
|
+
try {
|
|
213
|
+
scaffold = await scaffoldProject({
|
|
214
|
+
cancelSignal: scaffoldController.signal,
|
|
215
|
+
dataset: project.datasetName,
|
|
216
|
+
displayName,
|
|
217
|
+
output,
|
|
218
|
+
projectId: project.resourceId,
|
|
219
|
+
telemetry: this.telemetry,
|
|
220
|
+
token: project.token,
|
|
221
|
+
workDir: cwd
|
|
222
|
+
});
|
|
223
|
+
scaffoldController.signal.throwIfAborted();
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (scaffoldController.signal.aborted || err instanceof Error && err.message === 'SIGINT') {
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
228
|
+
// Scaffolding is best-effort and may leave files or directories behind when it fails.
|
|
229
|
+
// Partial filesystem state does not prove the scaffold is runnable, so report the failure,
|
|
230
|
+
// continue only with the project/token/claim handoff below, and do not attempt cleanup.
|
|
231
|
+
flow.note(`Automatic setup did not finish: ${err instanceof Error ? err.message : err}`);
|
|
232
|
+
flow.line('The project was created. You do not need to create another one.');
|
|
233
|
+
flow.gap();
|
|
234
|
+
} finally{
|
|
235
|
+
process.off('SIGINT', abortScaffold);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (scaffold) {
|
|
239
|
+
if (scaffold.frontendPath) {
|
|
240
|
+
flow.result('Created two folders');
|
|
241
|
+
flow.line(`./${STUDIO_DIR}: your Studio, where you write and edit content`);
|
|
242
|
+
flow.line(`./${FRONTEND_DIR}: your website, a Next.js app that reads it`);
|
|
243
|
+
flow.gap();
|
|
244
|
+
printStudioInstructions();
|
|
245
|
+
if (scaffold.frontendDependenciesInstalled === false) {
|
|
246
|
+
flow.gap();
|
|
247
|
+
if (scaffold.frontendDependencyError) {
|
|
248
|
+
flow.line(`Installing website dependencies failed: ${scaffold.frontendDependencyError}`);
|
|
249
|
+
flow.gap();
|
|
250
|
+
}
|
|
251
|
+
flow.highlight('Finish installing your website dependencies');
|
|
252
|
+
flow.gap();
|
|
253
|
+
flow.command(`cd ${FRONTEND_DIR} && ${frontendInstallCommand(scaffold.frontendPackageManager)}`);
|
|
254
|
+
}
|
|
255
|
+
flow.gap();
|
|
256
|
+
printWebsiteInstructions(scaffold.frontendPackageManager);
|
|
257
|
+
} else if (scaffold.detectedFramework) {
|
|
258
|
+
flow.result(`Created ./${STUDIO_DIR} for your existing ${scaffold.detectedFramework} app`);
|
|
259
|
+
flow.line(`./${STUDIO_DIR}: your Studio, where you write and edit content`);
|
|
260
|
+
flow.line(`Your existing ${scaffold.detectedFramework} frontend was left unchanged.`);
|
|
261
|
+
flow.gap();
|
|
262
|
+
printStudioInstructions();
|
|
263
|
+
} else {
|
|
264
|
+
flow.result(`Created ./${STUDIO_DIR}`);
|
|
265
|
+
flow.line(`./${STUDIO_DIR}: your Studio, where you write and edit content`);
|
|
266
|
+
flow.line(`The website was not created: ${scaffold.frontendCreationError ?? 'automatic setup did not finish'}`);
|
|
267
|
+
flow.gap();
|
|
268
|
+
printStudioInstructions();
|
|
269
|
+
}
|
|
270
|
+
flow.gap();
|
|
271
|
+
}
|
|
272
|
+
if (recorded === false && !json) {
|
|
273
|
+
flow.note('The local recovery record was not saved.');
|
|
274
|
+
flow.line('Keep the claim URL and access token from this output.');
|
|
275
|
+
flow.gap();
|
|
276
|
+
}
|
|
277
|
+
if (!json) {
|
|
278
|
+
if (!scaffold && !this.flags.scaffold) {
|
|
279
|
+
flow.result('Project created without scaffolding');
|
|
280
|
+
flow.line('No folders or env files were created. Use the project ID and dataset above in your own setup.');
|
|
281
|
+
flow.gap();
|
|
282
|
+
flow.link(`http://localhost:3333/#token=${encodeURIComponent(project.token)}`, {
|
|
283
|
+
label: 'Once a Studio is running on http://localhost:3333, sign in with:'
|
|
284
|
+
});
|
|
285
|
+
flow.gap();
|
|
286
|
+
flow.line('The token signs you in: there is no account yet.');
|
|
287
|
+
flow.gap();
|
|
288
|
+
}
|
|
289
|
+
flow.line('Your content is private until you claim, to read it, you need the token.');
|
|
290
|
+
flow.line("Treat your token as a password and don't expose it publicly in your app.");
|
|
291
|
+
flow.gap();
|
|
292
|
+
flow.line('The claim link and token are printed above but you can recover them ' + `with ${styleText('cyan', 'sanity projects unclaimed')} before the timer runs out on ${formatClaimDeadline(project.expiresAt)}.`);
|
|
293
|
+
flow.gap();
|
|
294
|
+
flow.line(project.termsNotice);
|
|
295
|
+
flow.link(project.termsUrl, {
|
|
296
|
+
label: 'Terms of Service:'
|
|
297
|
+
});
|
|
298
|
+
flow.gap();
|
|
299
|
+
flow.line('For more information on how claiming works or how to build your app');
|
|
300
|
+
flow.link(SANITY_NEW_URL, {
|
|
301
|
+
label: 'visit',
|
|
302
|
+
outro: true
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return toResult(project);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
//# sourceMappingURL=new.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/new.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Args, Flags} from '@oclif/core'\nimport {CLIError} from '@oclif/core/errors'\nimport {exitCodes} from '@sanity/cli-core'\nimport {SanityCommand} from '@sanity/cli-core/SanityCommand'\nimport {createFlow, input} from '@sanity/cli-core/ux'\n\nimport {\n FRONTEND_DIR,\n FRONTEND_ENV_FILE,\n getUnavailableScaffoldTarget,\n scaffoldProject,\n type ScaffoldResult,\n STUDIO_DIR,\n STUDIO_ENV_FILE,\n} from '../actions/scaffold/scaffoldProject.js'\nimport {type MintedProject, mintUnclaimedProject} from '../services/mintProject.js'\nimport {formatClaimDeadline} from '../util/formatClaimDeadline.js'\nimport {CLAIM_WINDOW_HOURS, SANITY_NEW_URL} from '../util/mintProjectConstants.js'\nimport {renderNewCommandSplash} from '../util/newCommandSplash.js'\nimport {recordUnclaimedProject} from '../util/unclaimedProjects.js'\n\nconst DEFAULT_PROJECT_NAME = 'My Sanity project'\nconst HELP_DESCRIPTION_WIDTH = 78\n\nfunction formatHelpDescription(...paragraphs: string[]): string {\n return paragraphs\n .map((paragraph) => {\n const lines: string[] = []\n let line = ''\n\n for (const word of paragraph.split(/\\s+/u)) {\n if (!line || line.length + word.length + 1 <= HELP_DESCRIPTION_WIDTH) {\n line = line ? `${line} ${word}` : word\n } else {\n lines.push(line)\n line = word\n }\n }\n\n if (line) lines.push(line)\n return lines.join('\\n')\n })\n .join('\\n\\n')\n}\n\nconst FRONTEND_DEV_COMMANDS: Record<\n NonNullable<ScaffoldResult['frontendPackageManager']>,\n string\n> = {\n bun: 'bun dev',\n manual: 'npm run dev',\n npm: 'npm run dev',\n pnpm: 'pnpm dev',\n yarn: 'yarn dev',\n}\n\nfunction frontendDevCommand(packageManager: ScaffoldResult['frontendPackageManager']): string {\n return packageManager ? FRONTEND_DEV_COMMANDS[packageManager] : 'npm run dev'\n}\n\nfunction frontendInstallCommand(packageManager: ScaffoldResult['frontendPackageManager']): string {\n switch (packageManager) {\n case 'bun': {\n return 'bun add next-sanity'\n }\n case 'pnpm': {\n return 'pnpm add --save-prod next-sanity'\n }\n case 'yarn': {\n return 'yarn add next-sanity'\n }\n default: {\n return 'npm install next-sanity'\n }\n }\n}\n\nexport interface NewProjectResult {\n apiHost: string\n claimApiUrl: string\n claimToken: string\n claimUrl: string\n dataset: string\n expiresAt: string\n projectId: string\n terms: {\n notice: string\n url: string\n }\n token: string\n}\n\nfunction toResult(project: MintedProject): NewProjectResult {\n return {\n apiHost: project.apiHost,\n claimApiUrl: project.claimApiUrl,\n claimToken: project.claimToken,\n claimUrl: project.claimUrl,\n dataset: project.datasetName,\n expiresAt: project.expiresAt,\n projectId: project.resourceId,\n terms: {notice: project.termsNotice, url: project.termsUrl},\n token: project.token,\n }\n}\n\nexport class NewCommand extends SanityCommand<typeof NewCommand> {\n static override args = {\n projectName: Args.string({\n description: 'Display name for the new project',\n required: false,\n }),\n }\n\n static override description = formatHelpDescription(\n `Sets up two folders here: ./${STUDIO_DIR}, a Studio where you write and edit your content, ` +\n `and ./${FRONTEND_DIR}, a Next.js website that reads it. Both are already connected to your ` +\n 'new project, so you can start them straight away. Use --no-scaffold if you just want the ' +\n 'project and nothing else.',\n `The project is real and works immediately, but it is only yours for ${CLAIM_WINDOW_HOURS} hours. Claim it with ` +\n 'a Sanity account before the deadline and everything you have built stays exactly as it is. ' +\n 'Claiming is free and takes about a minute. Miss the deadline and the project and its ' +\n 'content are deleted.',\n 'Two things to keep private: the claim link, because anyone who opens it becomes the owner, ' +\n `and the access token saved in ./${STUDIO_ENV_FILE} and ./${FRONTEND_ENV_FILE}, because it ` +\n 'can read and change everything in the project. Keep both env files out of git, and never ' +\n 'put the token in code that runs in the browser.',\n `Fetch ${SANITY_NEW_URL} for full instructions, or point your AI agent at it.`,\n )\n\n static override enableJsonFlag = true\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Create a project with a Studio and a website',\n },\n {\n command: '<%= config.bin %> <%= command.id %> \"My New Project\"',\n description: 'Create a project called \"My New Project\"',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --yes',\n description: 'Create a project without being asked anything',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --no-scaffold',\n description: 'Create the project only, with no Studio or website',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --json',\n description: 'Create a project and print its details as JSON',\n },\n ]\n\n static override flags = {\n scaffold: Flags.boolean({\n allowNo: true,\n default: true,\n description: `Set up a Studio in ./${STUDIO_DIR} and a Next.js website in ./${FRONTEND_DIR} (on by default)`,\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description: `Skip prompts and use defaults (project: \"${DEFAULT_PROJECT_NAME}\")`,\n }),\n }\n\n static override summary = `Create a Sanity project without an account, and claim it within ${CLAIM_WINDOW_HOURS} hours to keep it.`\n\n public async run(): Promise<NewProjectResult> {\n const {output} = this\n const json = this.jsonEnabled()\n const flow = createFlow(output.log)\n const invoked = [this.config.bin, ...(this.id?.split(':') ?? [])].join(' ')\n const cwd = process.cwd()\n\n const unavailableScaffoldTarget =\n !json && this.flags.scaffold ? await getUnavailableScaffoldTarget(cwd) : undefined\n if (unavailableScaffoldTarget) {\n throw new CLIError(\n `./${unavailableScaffoldTarget} is not an empty directory. No project was created.`,\n {\n code:\n unavailableScaffoldTarget === STUDIO_DIR\n ? 'EXISTING_STUDIO_DIRECTORY'\n : 'EXISTING_FRONTEND_DIRECTORY',\n exit: exitCodes.RUNTIME_ERROR,\n suggestions: [\n `Run this command where ./${unavailableScaffoldTarget} does not exist or is an empty directory`,\n `Or run \\`${invoked} --no-scaffold\\` to create the project without changing ./${unavailableScaffoldTarget}`,\n ],\n },\n )\n }\n\n if (!json) {\n renderNewCommandSplash(output.log)\n flow.intro('Setting up your Sanity project.')\n flow.gap()\n\n if (!this.isUnattended()) {\n flow.note(\n `${styleText('cyan', `${invoked} --yes`)} for a non-interactive flow with defaults.`,\n )\n flow.gap()\n }\n }\n\n let displayName = this.args.projectName?.trim()\n if (!displayName && this.isUnattended()) {\n displayName = DEFAULT_PROJECT_NAME\n } else if (!displayName) {\n displayName =\n (await input({default: DEFAULT_PROJECT_NAME, message: 'Project name'})).trim() ||\n DEFAULT_PROJECT_NAME\n }\n\n const spin = json ? undefined : flow.spin('Creating your project...')\n let project: MintedProject\n try {\n project = await mintUnclaimedProject({displayName})\n } catch (err) {\n spin?.fail('Creating your project failed.')\n throw err\n }\n\n const recorded = recordUnclaimedProject(project)\n spin?.succeed(`Created \"${displayName}\"`)\n\n if (!json) {\n flow.result(`Project ID: ${styleText('cyan', project.resourceId)}`)\n flow.result(`Dataset: ${styleText('cyan', project.datasetName)} (where your content lives)`)\n flow.result(`Token: ${styleText('cyan', project.token)}`)\n flow.link(project.claimUrl, {label: 'Claim link:'})\n flow.gap()\n flow.highlight(\n `Claim your project by ${styleText('yellow', formatClaimDeadline(project.expiresAt))} to avoid losing it.`,\n )\n flow.gap()\n flow.line('To use the Sanity CLI before claiming use your token by setting')\n flow.command(`SANITY_AUTH_TOKEN=\"${project.token}\" sanity \"command\"`)\n flow.gap()\n flow.note(\n styleText(\n 'dim',\n 'If you are an agent: give this claim URL to the person you are working for. They have ' +\n 'to open it themselves before the deadline.',\n ),\n )\n flow.gap()\n }\n\n const printStudioInstructions = () => {\n flow.highlight('Start your Studio:')\n flow.gap()\n flow.command(`cd ${STUDIO_DIR} && npx sanity dev`)\n flow.gap()\n flow.link(`http://localhost:3333/#token=${encodeURIComponent(project.token)}`, {\n label: 'Then open this link:',\n })\n flow.gap()\n flow.line('The token signs you in: there is no account yet.')\n }\n const printWebsiteInstructions = (packageManager: ScaffoldResult['frontendPackageManager']) => {\n flow.highlight('In a separate terminal, start your website:')\n flow.gap()\n flow.command(`cd ${FRONTEND_DIR} && ${frontendDevCommand(packageManager)}`)\n flow.gap()\n flow.link('http://localhost:3000', {label: 'Then open this link:'})\n }\n\n let scaffold: ScaffoldResult | undefined\n if (!json && this.flags.scaffold) {\n const scaffoldController = new AbortController()\n const abortScaffold = () => scaffoldController.abort(new Error('SIGINT'))\n process.once('SIGINT', abortScaffold)\n try {\n scaffold = await scaffoldProject({\n cancelSignal: scaffoldController.signal,\n dataset: project.datasetName,\n displayName,\n output,\n projectId: project.resourceId,\n telemetry: this.telemetry,\n token: project.token,\n workDir: cwd,\n })\n scaffoldController.signal.throwIfAborted()\n } catch (err) {\n if (\n scaffoldController.signal.aborted ||\n (err instanceof Error && err.message === 'SIGINT')\n ) {\n throw err\n }\n // Scaffolding is best-effort and may leave files or directories behind when it fails.\n // Partial filesystem state does not prove the scaffold is runnable, so report the failure,\n // continue only with the project/token/claim handoff below, and do not attempt cleanup.\n flow.note(`Automatic setup did not finish: ${err instanceof Error ? err.message : err}`)\n flow.line('The project was created. You do not need to create another one.')\n flow.gap()\n } finally {\n process.off('SIGINT', abortScaffold)\n }\n }\n\n if (scaffold) {\n if (scaffold.frontendPath) {\n flow.result('Created two folders')\n flow.line(`./${STUDIO_DIR}: your Studio, where you write and edit content`)\n flow.line(`./${FRONTEND_DIR}: your website, a Next.js app that reads it`)\n flow.gap()\n printStudioInstructions()\n if (scaffold.frontendDependenciesInstalled === false) {\n flow.gap()\n if (scaffold.frontendDependencyError) {\n flow.line(`Installing website dependencies failed: ${scaffold.frontendDependencyError}`)\n flow.gap()\n }\n flow.highlight('Finish installing your website dependencies')\n flow.gap()\n flow.command(\n `cd ${FRONTEND_DIR} && ${frontendInstallCommand(scaffold.frontendPackageManager)}`,\n )\n }\n flow.gap()\n printWebsiteInstructions(scaffold.frontendPackageManager)\n } else if (scaffold.detectedFramework) {\n flow.result(`Created ./${STUDIO_DIR} for your existing ${scaffold.detectedFramework} app`)\n flow.line(`./${STUDIO_DIR}: your Studio, where you write and edit content`)\n flow.line(`Your existing ${scaffold.detectedFramework} frontend was left unchanged.`)\n flow.gap()\n printStudioInstructions()\n } else {\n flow.result(`Created ./${STUDIO_DIR}`)\n flow.line(`./${STUDIO_DIR}: your Studio, where you write and edit content`)\n flow.line(\n `The website was not created: ${scaffold.frontendCreationError ?? 'automatic setup did not finish'}`,\n )\n flow.gap()\n printStudioInstructions()\n }\n flow.gap()\n }\n\n if (recorded === false && !json) {\n flow.note('The local recovery record was not saved.')\n flow.line('Keep the claim URL and access token from this output.')\n flow.gap()\n }\n\n if (!json) {\n if (!scaffold && !this.flags.scaffold) {\n flow.result('Project created without scaffolding')\n flow.line(\n 'No folders or env files were created. Use the project ID and dataset above in your own setup.',\n )\n flow.gap()\n flow.link(`http://localhost:3333/#token=${encodeURIComponent(project.token)}`, {\n label: 'Once a Studio is running on http://localhost:3333, sign in with:',\n })\n flow.gap()\n flow.line('The token signs you in: there is no account yet.')\n flow.gap()\n }\n\n flow.line('Your content is private until you claim, to read it, you need the token.')\n flow.line(\"Treat your token as a password and don't expose it publicly in your app.\")\n flow.gap()\n flow.line(\n 'The claim link and token are printed above but you can recover them ' +\n `with ${styleText('cyan', 'sanity projects unclaimed')} before the timer runs out on ${formatClaimDeadline(project.expiresAt)}.`,\n )\n flow.gap()\n flow.line(project.termsNotice)\n flow.link(project.termsUrl, {label: 'Terms of Service:'})\n flow.gap()\n flow.line('For more information on how claiming works or how to build your app')\n flow.link(SANITY_NEW_URL, {label: 'visit', outro: true})\n }\n\n return toResult(project)\n }\n}\n"],"names":["styleText","Args","Flags","CLIError","exitCodes","SanityCommand","createFlow","input","FRONTEND_DIR","FRONTEND_ENV_FILE","getUnavailableScaffoldTarget","scaffoldProject","STUDIO_DIR","STUDIO_ENV_FILE","mintUnclaimedProject","formatClaimDeadline","CLAIM_WINDOW_HOURS","SANITY_NEW_URL","renderNewCommandSplash","recordUnclaimedProject","DEFAULT_PROJECT_NAME","HELP_DESCRIPTION_WIDTH","formatHelpDescription","paragraphs","map","paragraph","lines","line","word","split","length","push","join","FRONTEND_DEV_COMMANDS","bun","manual","npm","pnpm","yarn","frontendDevCommand","packageManager","frontendInstallCommand","toResult","project","apiHost","claimApiUrl","claimToken","claimUrl","dataset","datasetName","expiresAt","projectId","resourceId","terms","notice","termsNotice","url","termsUrl","token","NewCommand","args","projectName","string","description","required","enableJsonFlag","examples","command","flags","scaffold","boolean","allowNo","default","yes","char","summary","run","output","json","jsonEnabled","flow","log","invoked","config","bin","id","cwd","process","unavailableScaffoldTarget","undefined","code","exit","RUNTIME_ERROR","suggestions","intro","gap","isUnattended","note","displayName","trim","message","spin","err","fail","recorded","succeed","result","link","label","highlight","printStudioInstructions","encodeURIComponent","printWebsiteInstructions","scaffoldController","AbortController","abortScaffold","abort","Error","once","cancelSignal","signal","telemetry","workDir","throwIfAborted","aborted","off","frontendPath","frontendDependenciesInstalled","frontendDependencyError","frontendPackageManager","detectedFramework","frontendCreationError","outro"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/organizations/list.ts"],"sourcesContent":["import {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {Table} from 'console-table-printer'\n\nimport {listOrganizations} from '../../services/organizations.js'\nimport {organizationAliases} from '../../util/organizationAliases.js'\n\nconst listOrgsDebug = subdebug('organizations:list')\n\nexport class ListOrganizationsCommand extends SanityCommand<typeof ListOrganizationsCommand> {\n static override description = 'List organizations you are a member of'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List all your organizations',\n },\n ]\n\n static override hiddenAliases = organizationAliases('list')\n\n public async run(): Promise<void> {\n let organizations\n try {\n organizations = await listOrganizations()\n } catch (error) {\n listOrgsDebug('Error listing organizations', error)\n this.error(`Failed to list organizations: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n if (organizations.length === 0) {\n this.log('No organizations found')\n return\n }\n\n const table = new Table({\n columns: [\n {alignment: 'left', name: 'id', title: 'ID'},\n {alignment: 'left', name: 'name', title: 'Name'},\n {alignment: 'left', name: 'slug', title: 'Slug'},\n ],\n })\n\n for (const {id, name, slug} of organizations) {\n table.addRow({id, name, slug: slug ?? '-'})\n }\n\n table.
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/organizations/list.ts"],"sourcesContent":["import {exitCodes, SanityCommand, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {Table} from 'console-table-printer'\n\nimport {listOrganizations} from '../../services/organizations.js'\nimport {organizationAliases} from '../../util/organizationAliases.js'\n\nconst listOrgsDebug = subdebug('organizations:list')\n\nexport class ListOrganizationsCommand extends SanityCommand<typeof ListOrganizationsCommand> {\n static override description = 'List organizations you are a member of'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List all your organizations',\n },\n ]\n\n static override hiddenAliases = organizationAliases('list')\n\n public async run(): Promise<void> {\n let organizations\n try {\n organizations = await listOrganizations()\n } catch (error) {\n listOrgsDebug('Error listing organizations', error)\n this.error(`Failed to list organizations: ${getErrorMessage(error)}`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n if (organizations.length === 0) {\n this.log('No organizations found')\n return\n }\n\n const table = new Table({\n columns: [\n {alignment: 'left', name: 'id', title: 'ID'},\n {alignment: 'left', name: 'name', title: 'Name'},\n {alignment: 'left', name: 'slug', title: 'Slug'},\n ],\n })\n\n for (const {id, name, slug} of organizations) {\n table.addRow({id, name, slug: slug ?? '-'})\n }\n\n this.log(table.render())\n }\n}\n"],"names":["exitCodes","SanityCommand","subdebug","getErrorMessage","Table","listOrganizations","organizationAliases","listOrgsDebug","ListOrganizationsCommand","description","examples","command","hiddenAliases","run","organizations","error","exit","RUNTIME_ERROR","length","log","table","columns","alignment","name","title","id","slug","addRow","render"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,aAAa,EAAEC,QAAQ,QAAO,mBAAkB;AACnE,SAAQC,eAAe,QAAO,0BAAyB;AACvD,SAAQC,KAAK,QAAO,wBAAuB;AAE3C,SAAQC,iBAAiB,QAAO,kCAAiC;AACjE,SAAQC,mBAAmB,QAAO,oCAAmC;AAErE,MAAMC,gBAAgBL,SAAS;AAE/B,OAAO,MAAMM,iCAAiCP;IAC5C,OAAgBQ,cAAc,yCAAwC;IAEtE,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,gBAAgBN,oBAAoB,QAAO;IAE3D,MAAaO,MAAqB;QAChC,IAAIC;QACJ,IAAI;YACFA,gBAAgB,MAAMT;QACxB,EAAE,OAAOU,OAAO;YACdR,cAAc,+BAA+BQ;YAC7C,IAAI,CAACA,KAAK,CAAC,CAAC,8BAA8B,EAAEZ,gBAAgBY,QAAQ,EAAE;gBACpEC,MAAMhB,UAAUiB,aAAa;YAC/B;QACF;QAEA,IAAIH,cAAcI,MAAM,KAAK,GAAG;YAC9B,IAAI,CAACC,GAAG,CAAC;YACT;QACF;QAEA,MAAMC,QAAQ,IAAIhB,MAAM;YACtBiB,SAAS;gBACP;oBAACC,WAAW;oBAAQC,MAAM;oBAAMC,OAAO;gBAAI;gBAC3C;oBAACF,WAAW;oBAAQC,MAAM;oBAAQC,OAAO;gBAAM;gBAC/C;oBAACF,WAAW;oBAAQC,MAAM;oBAAQC,OAAO;gBAAM;aAChD;QACH;QAEA,KAAK,MAAM,EAACC,EAAE,EAAEF,IAAI,EAAEG,IAAI,EAAC,IAAIZ,cAAe;YAC5CM,MAAMO,MAAM,CAAC;gBAACF;gBAAIF;gBAAMG,MAAMA,QAAQ;YAAG;QAC3C;QAEA,IAAI,CAACP,GAAG,CAACC,MAAMQ,MAAM;IACvB;AACF"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
import { Flags } from '@oclif/core';
|
|
3
|
+
import { exitCodes, SanityCommand } from '@sanity/cli-core';
|
|
4
|
+
import { logSymbols } from '@sanity/cli-core/ux';
|
|
5
|
+
import size from 'lodash-es/size.js';
|
|
6
|
+
import { formatKeyValue, sectionHeader } from '../../actions/debug/output.js';
|
|
7
|
+
import { getProjectClaimStatus } from '../../services/projects.js';
|
|
8
|
+
import { readUnclaimedProjects, removeUnclaimedProject } from '../../util/unclaimedProjects.js';
|
|
9
|
+
const listFields = [
|
|
10
|
+
'id',
|
|
11
|
+
'dataset',
|
|
12
|
+
'created',
|
|
13
|
+
'claim deadline',
|
|
14
|
+
'claim url'
|
|
15
|
+
];
|
|
16
|
+
function recordRow(record) {
|
|
17
|
+
return [
|
|
18
|
+
record.projectId,
|
|
19
|
+
record.dataset,
|
|
20
|
+
record.mintedAt,
|
|
21
|
+
record.expiresAt,
|
|
22
|
+
record.claimUrl
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
function isExpired(record) {
|
|
26
|
+
return Date.parse(record.expiresAt) <= Date.now();
|
|
27
|
+
}
|
|
28
|
+
function formatTable(fields, rows) {
|
|
29
|
+
const maxWidths = fields.map((field)=>size(field));
|
|
30
|
+
for (const row of rows){
|
|
31
|
+
for (const [index, value] of row.entries()){
|
|
32
|
+
maxWidths[index] = Math.max(size(value), maxWidths[index]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const formatRow = (row)=>row.map((value, index)=>value.padEnd(maxWidths[index])).join(' ');
|
|
36
|
+
return [
|
|
37
|
+
styleText('cyan', formatRow(fields)),
|
|
38
|
+
...rows.map((row)=>formatRow(row))
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
export class UnclaimedProjectsCommand extends SanityCommand {
|
|
42
|
+
static description = 'Recover details for unclaimed projects created on this machine';
|
|
43
|
+
static examples = [
|
|
44
|
+
{
|
|
45
|
+
command: '<%= config.bin %> <%= command.id %>',
|
|
46
|
+
description: 'List locally recorded unclaimed projects'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
command: '<%= config.bin %> <%= command.id %> --project-id abc123',
|
|
50
|
+
description: 'Show recovery details for one project'
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
static flags = {
|
|
54
|
+
'project-id': Flags.string({
|
|
55
|
+
description: 'Project ID to recover'
|
|
56
|
+
})
|
|
57
|
+
};
|
|
58
|
+
static hiddenAliases = [
|
|
59
|
+
'project:unclaimed'
|
|
60
|
+
];
|
|
61
|
+
async run() {
|
|
62
|
+
let records;
|
|
63
|
+
try {
|
|
64
|
+
records = readUnclaimedProjects();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
this.output.error(`Could not read local unclaimed projects: ${error instanceof Error ? error.message : String(error)} Remove or repair the "unclaimedProjects" entry in your Sanity user config.`, {
|
|
67
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const projectId = this.flags['project-id'];
|
|
72
|
+
if (projectId) {
|
|
73
|
+
const record = records.find((candidate)=>candidate.projectId === projectId);
|
|
74
|
+
if (!record) {
|
|
75
|
+
this.output.error(`No local recovery record found for project "${projectId}". Run \`sanity projects unclaimed\` to list recorded projects.`, {
|
|
76
|
+
exit: exitCodes.RUNTIME_ERROR
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (isExpired(record)) {
|
|
81
|
+
if (this.removeExpiredProject(record)) {
|
|
82
|
+
this.logExpiredProject(record.projectId);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const status = await getProjectClaimStatus(record.projectId, record.token);
|
|
87
|
+
if (status === 'claimed') {
|
|
88
|
+
if (this.removeClaimedProject(record)) {
|
|
89
|
+
this.logClaimedProject(record.projectId);
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (status === 'unknown') {
|
|
94
|
+
this.output.warn(`Could not verify whether project "${projectId}" is claimed. Local recovery details were kept. Run this command again to retry.`);
|
|
95
|
+
}
|
|
96
|
+
this.output.log(sectionHeader('Project'));
|
|
97
|
+
const details = [
|
|
98
|
+
[
|
|
99
|
+
'Project ID',
|
|
100
|
+
record.projectId
|
|
101
|
+
],
|
|
102
|
+
[
|
|
103
|
+
'Dataset',
|
|
104
|
+
record.dataset
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
'Created',
|
|
108
|
+
record.mintedAt
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
'Claim deadline',
|
|
112
|
+
record.expiresAt
|
|
113
|
+
],
|
|
114
|
+
[
|
|
115
|
+
'Claim URL',
|
|
116
|
+
record.claimUrl
|
|
117
|
+
],
|
|
118
|
+
[
|
|
119
|
+
'Access token',
|
|
120
|
+
record.token
|
|
121
|
+
]
|
|
122
|
+
];
|
|
123
|
+
for (const [label, value] of details){
|
|
124
|
+
this.output.log(formatKeyValue(label, value, {
|
|
125
|
+
padTo: 14
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
this.output.log();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const expiredProjectIds = [];
|
|
132
|
+
const activeRecords = records.filter((record)=>{
|
|
133
|
+
if (!isExpired(record)) return true;
|
|
134
|
+
if (this.removeExpiredProject(record)) {
|
|
135
|
+
expiredProjectIds.push(record.projectId);
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
});
|
|
139
|
+
const checked = await Promise.all(activeRecords.map(async (record)=>({
|
|
140
|
+
record,
|
|
141
|
+
status: await getProjectClaimStatus(record.projectId, record.token)
|
|
142
|
+
})));
|
|
143
|
+
records = [];
|
|
144
|
+
const claimedProjectIds = [];
|
|
145
|
+
for (const { record, status } of checked){
|
|
146
|
+
if (status === 'claimed') {
|
|
147
|
+
if (this.removeClaimedProject(record)) {
|
|
148
|
+
claimedProjectIds.push(record.projectId);
|
|
149
|
+
}
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (status === 'unknown') {
|
|
153
|
+
this.output.warn(`Could not verify whether project "${record.projectId}" is claimed. Local recovery details were kept. Run this command again to retry.`);
|
|
154
|
+
}
|
|
155
|
+
records.push(record);
|
|
156
|
+
}
|
|
157
|
+
if (records.length === 0) {
|
|
158
|
+
if (claimedProjectIds.length > 0 || expiredProjectIds.length > 0) {
|
|
159
|
+
for (const expiredProjectId of expiredProjectIds){
|
|
160
|
+
this.logExpiredProject(expiredProjectId);
|
|
161
|
+
}
|
|
162
|
+
for (const claimedProjectId of claimedProjectIds){
|
|
163
|
+
this.logClaimedProject(claimedProjectId);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this.output.log('No locally recorded unclaimed projects. Create one with `sanity new`, then return here if you need its recovery details.');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
for (const line of formatTable(listFields, records.map((record)=>recordRow(record)))){
|
|
171
|
+
this.output.log(line);
|
|
172
|
+
}
|
|
173
|
+
if (claimedProjectIds.length > 0 || expiredProjectIds.length > 0) {
|
|
174
|
+
this.output.log();
|
|
175
|
+
for (const expiredProjectId of expiredProjectIds){
|
|
176
|
+
this.logExpiredProject(expiredProjectId);
|
|
177
|
+
}
|
|
178
|
+
for (const claimedProjectId of claimedProjectIds){
|
|
179
|
+
this.logClaimedProject(claimedProjectId);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
logClaimedProject(projectId) {
|
|
184
|
+
this.output.log(`${logSymbols.success} Project "${projectId}" is claimed. Local recovery details removed.`);
|
|
185
|
+
}
|
|
186
|
+
logExpiredProject(projectId) {
|
|
187
|
+
this.output.log(`${logSymbols.success} Project "${projectId}" expired. Local recovery details removed.`);
|
|
188
|
+
}
|
|
189
|
+
removeClaimedProject(record) {
|
|
190
|
+
const removed = removeUnclaimedProject(record.projectId, record.claimToken);
|
|
191
|
+
if (!removed) {
|
|
192
|
+
this.output.warn(`Project "${record.projectId}" is claimed, but its local recovery details could not be removed. Run this command again to retry.`);
|
|
193
|
+
}
|
|
194
|
+
return removed;
|
|
195
|
+
}
|
|
196
|
+
removeExpiredProject(record) {
|
|
197
|
+
const removed = removeUnclaimedProject(record.projectId, record.claimToken);
|
|
198
|
+
if (!removed) {
|
|
199
|
+
this.output.warn(`Project "${record.projectId}" expired, but its local recovery details could not be removed. Run this command again to retry.`);
|
|
200
|
+
}
|
|
201
|
+
return removed;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
//# sourceMappingURL=unclaimed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/projects/unclaimed.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {logSymbols} from '@sanity/cli-core/ux'\nimport size from 'lodash-es/size.js'\n\nimport {formatKeyValue, sectionHeader} from '../../actions/debug/output.js'\nimport {getProjectClaimStatus} from '../../services/projects.js'\nimport {\n readUnclaimedProjects,\n removeUnclaimedProject,\n type UnclaimedProjectRecord,\n} from '../../util/unclaimedProjects.js'\n\nconst listFields = ['id', 'dataset', 'created', 'claim deadline', 'claim url']\n\nfunction recordRow(record: UnclaimedProjectRecord): string[] {\n return [record.projectId, record.dataset, record.mintedAt, record.expiresAt, record.claimUrl]\n}\n\nfunction isExpired(record: UnclaimedProjectRecord): boolean {\n return Date.parse(record.expiresAt) <= Date.now()\n}\n\nfunction formatTable(fields: string[], rows: string[][]): string[] {\n const maxWidths = fields.map((field) => size(field))\n for (const row of rows) {\n for (const [index, value] of row.entries()) {\n maxWidths[index] = Math.max(size(value), maxWidths[index])\n }\n }\n\n const formatRow = (row: string[]) =>\n row.map((value, index) => value.padEnd(maxWidths[index])).join(' ')\n\n return [styleText('cyan', formatRow(fields)), ...rows.map((row) => formatRow(row))]\n}\n\nexport class UnclaimedProjectsCommand extends SanityCommand<typeof UnclaimedProjectsCommand> {\n static override description = 'Recover details for unclaimed projects created on this machine'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'List locally recorded unclaimed projects',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123',\n description: 'Show recovery details for one project',\n },\n ]\n\n static override flags = {\n 'project-id': Flags.string({\n description: 'Project ID to recover',\n }),\n }\n\n static override hiddenAliases: string[] = ['project:unclaimed']\n\n public async run(): Promise<void> {\n let records: UnclaimedProjectRecord[]\n try {\n records = readUnclaimedProjects()\n } catch (error) {\n this.output.error(\n `Could not read local unclaimed projects: ${\n error instanceof Error ? error.message : String(error)\n } Remove or repair the \"unclaimedProjects\" entry in your Sanity user config.`,\n {exit: exitCodes.RUNTIME_ERROR},\n )\n return\n }\n\n const projectId = this.flags['project-id']\n if (projectId) {\n const record = records.find((candidate) => candidate.projectId === projectId)\n if (!record) {\n this.output.error(\n `No local recovery record found for project \"${projectId}\". Run \\`sanity projects unclaimed\\` to list recorded projects.`,\n {exit: exitCodes.RUNTIME_ERROR},\n )\n return\n }\n\n if (isExpired(record)) {\n if (this.removeExpiredProject(record)) {\n this.logExpiredProject(record.projectId)\n }\n return\n }\n\n const status = await getProjectClaimStatus(record.projectId, record.token)\n if (status === 'claimed') {\n if (this.removeClaimedProject(record)) {\n this.logClaimedProject(record.projectId)\n }\n return\n }\n if (status === 'unknown') {\n this.output.warn(\n `Could not verify whether project \"${projectId}\" is claimed. Local recovery details were kept. Run this command again to retry.`,\n )\n }\n\n this.output.log(sectionHeader('Project'))\n const details = [\n ['Project ID', record.projectId],\n ['Dataset', record.dataset],\n ['Created', record.mintedAt],\n ['Claim deadline', record.expiresAt],\n ['Claim URL', record.claimUrl],\n ['Access token', record.token],\n ]\n for (const [label, value] of details) {\n this.output.log(formatKeyValue(label, value, {padTo: 14}))\n }\n this.output.log()\n return\n }\n\n const expiredProjectIds: string[] = []\n const activeRecords = records.filter((record) => {\n if (!isExpired(record)) return true\n if (this.removeExpiredProject(record)) {\n expiredProjectIds.push(record.projectId)\n }\n return false\n })\n const checked = await Promise.all(\n activeRecords.map(async (record) => ({\n record,\n status: await getProjectClaimStatus(record.projectId, record.token),\n })),\n )\n records = []\n const claimedProjectIds: string[] = []\n for (const {record, status} of checked) {\n if (status === 'claimed') {\n if (this.removeClaimedProject(record)) {\n claimedProjectIds.push(record.projectId)\n }\n continue\n }\n if (status === 'unknown') {\n this.output.warn(\n `Could not verify whether project \"${record.projectId}\" is claimed. Local recovery details were kept. Run this command again to retry.`,\n )\n }\n records.push(record)\n }\n\n if (records.length === 0) {\n if (claimedProjectIds.length > 0 || expiredProjectIds.length > 0) {\n for (const expiredProjectId of expiredProjectIds) {\n this.logExpiredProject(expiredProjectId)\n }\n for (const claimedProjectId of claimedProjectIds) {\n this.logClaimedProject(claimedProjectId)\n }\n return\n }\n this.output.log(\n 'No locally recorded unclaimed projects. Create one with `sanity new`, then return here if you need its recovery details.',\n )\n return\n }\n\n for (const line of formatTable(\n listFields,\n records.map((record) => recordRow(record)),\n )) {\n this.output.log(line)\n }\n if (claimedProjectIds.length > 0 || expiredProjectIds.length > 0) {\n this.output.log()\n for (const expiredProjectId of expiredProjectIds) {\n this.logExpiredProject(expiredProjectId)\n }\n for (const claimedProjectId of claimedProjectIds) {\n this.logClaimedProject(claimedProjectId)\n }\n }\n }\n\n private logClaimedProject(projectId: string): void {\n this.output.log(\n `${logSymbols.success} Project \"${projectId}\" is claimed. Local recovery details removed.`,\n )\n }\n\n private logExpiredProject(projectId: string): void {\n this.output.log(\n `${logSymbols.success} Project \"${projectId}\" expired. Local recovery details removed.`,\n )\n }\n\n private removeClaimedProject(record: UnclaimedProjectRecord): boolean {\n const removed = removeUnclaimedProject(record.projectId, record.claimToken)\n if (!removed) {\n this.output.warn(\n `Project \"${record.projectId}\" is claimed, but its local recovery details could not be removed. Run this command again to retry.`,\n )\n }\n return removed\n }\n\n private removeExpiredProject(record: UnclaimedProjectRecord): boolean {\n const removed = removeUnclaimedProject(record.projectId, record.claimToken)\n if (!removed) {\n this.output.warn(\n `Project \"${record.projectId}\" expired, but its local recovery details could not be removed. Run this command again to retry.`,\n )\n }\n return removed\n }\n}\n"],"names":["styleText","Flags","exitCodes","SanityCommand","logSymbols","size","formatKeyValue","sectionHeader","getProjectClaimStatus","readUnclaimedProjects","removeUnclaimedProject","listFields","recordRow","record","projectId","dataset","mintedAt","expiresAt","claimUrl","isExpired","Date","parse","now","formatTable","fields","rows","maxWidths","map","field","row","index","value","entries","Math","max","formatRow","padEnd","join","UnclaimedProjectsCommand","description","examples","command","flags","string","hiddenAliases","run","records","error","output","Error","message","String","exit","RUNTIME_ERROR","find","candidate","removeExpiredProject","logExpiredProject","status","token","removeClaimedProject","logClaimedProject","warn","log","details","label","padTo","expiredProjectIds","activeRecords","filter","push","checked","Promise","all","claimedProjectIds","length","expiredProjectId","claimedProjectId","line","success","removed","claimToken"],"mappings":"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"}
|